MySQL 8.0 新特性之橫向(LATERAL)派生表
作者: 不剪發(fā)的Tony老師
畢業(yè)于北京航空航天大學,十多年數(shù)據(jù)庫管理與開發(fā)經(jīng)驗,目前在一家全球性的金融公司從事數(shù)據(jù)庫架構設計。CSDN學院簽約講師以及GitChat專欄作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net
MySQL 將FROM中的子查詢稱為派生表(Derived Table)。以下查詢使用了一個派生表:
SELECT * FROM (SELECT 1) AS dt;
+---+
| 1 |
+---+
| 1 |
+---+
不過,MySQL 中的派生表存在一些限制:
派生表不能是關聯(lián)子查詢
派生表不能引用它所在的SELECT語句中的其他表
在 MySQL 8.0.14 之前,派生表不能引用它所在的SELECT語句外部的表
簡單來說,就是派生表必須能夠單獨運行,而不能依賴其他表。
從 MySQL 8.0.14 開始,派生表支持LATERAL關鍵字前綴,表示允許派生表引用它所在的FROM子句中的其他表。橫向派生表能夠完成普通派生表無法完成或者效率低下的操作。
考慮以下應用場景:departments 表存儲了部門的信息,employees 表存儲了員工信息。如何查找每個部門中薪水最高的 Top 5 和對應的員工?
示例表和數(shù)據(jù)
首先,使用傳統(tǒng)的方法很難實現(xiàn)這樣的功能:
SELECT d.department_name,
(SELECT e.salary
FROM employees e
WHERE e.department_id = d.department_id
ORDER BY e.salary DESC LIMIT 5
)
FROM departments d;
ERROR 1242 (21000): Subquery returns more than 1 row
以上查詢失敗的原因在于SELECT子查詢只能返回 1 條數(shù)據(jù)。
按照需求,我們可以先按照部門編號對員工信息進行分組,獲得每個組內(nèi)的薪水最高的 5 個員工,然后和部門表進行連接查詢:
SELECT d.department_name, t.first_name, t.last_name, t.salary
FROM departments d
LEFT JOIN (SELECT e.department_id, e.first_name, e.last_name, e.salary
FROM employees e
WHERE e.department_id = d.department_id
ORDER BY e.salary DESC LIMIT 5) t
ON d.department_id = t.department_id
ORDER BY d.department_name, t.salary DESC;
ERROR 1054 (42S22): Unknown column 'd.department_id' in 'where clause'
以上語句失敗的原因在于子查詢 t 不能引用外部查詢中的 departments 表。
可以利用 MySQL 中的自定義變量實現(xiàn)該功能:
SELECT d.department_name, w.first_name, w.last_name, w.salary, w.rn
FROM departments d
LEFT JOIN (
SELECT *
FROM (
SELECT a.*, if(@did = a.department_id, @rn := @rn+1, @rn := 1) AS rn, @did := a.department_id AS did
FROM (SELECT e.* FROM employees e ORDER BY department_id, salary DESC) a,
(SELECT @rn := 0 rn, @did := 0) b
) AS t
WHERE t.rn <= 5
) AS w
ON d.department_id = w.department_id
ORDER BY d.department_name, w.salary DESC;
+----------------------+-------------+-----------+----------+------+
| department_name | first_name | last_name | salary | rn |
+----------------------+-------------+-----------+----------+------+
| Accounting | Shelley | Higgins | 12008.00 | 1 |
| Accounting | William | Gietz | 8300.00 | 2 |
| Administration | Jennifer | Whalen | 4400.00 | 1 |
| Benefits | NULL | NULL | NULL | NULL |
| Construction | NULL | NULL | NULL | NULL |
| Contracting | NULL | NULL | NULL | NULL |
| Control And Credit | NULL | NULL | NULL | NULL |
| Corporate Tax | NULL | NULL | NULL | NULL |
| Executive | Steven | King | 24000.00 | 1 |
| Executive | Neena | Kochhar | 17000.00 | 2 |
| Executive | Lex | De Haan | 17000.00 | 3 |
| Finance | Nancy | Greenberg | 12008.00 | 1 |
| Finance | Daniel | Faviet | 9000.00 | 2 |
| Finance | John | Chen | 8200.00 | 3 |
| Finance | Jose Manuel | Urman | 7800.00 | 4 |
| Finance | Ismael | Sciarra | 7700.00 | 5 |
| Government Sales | NULL | NULL | NULL | NULL |
| Human Resources | Susan | Mavris | 6500.00 | 1 |
...
51 rows in set (0.00 sec)
可以看出,這種方法比較復雜,也不具有通用性。
現(xiàn)在來看一下如何使用LATERAL派生表實現(xiàn)這個需求:
SELECT d.department_name, t.first_name, t.last_name, t.salary
FROM departments d
LEFT JOIN LATERAL (SELECT e.department_id, e.first_name, e.last_name, e.salary
FROM employees e
WHERE e.department_id = d.department_id
ORDER BY e.salary DESC LIMIT 5) t
ON d.department_id = t.department_id
ORDER BY d.department_name, t.salary DESC;
+----------------------+-------------+-----------+----------+------+
| department_name | first_name | last_name | salary | rn |
+----------------------+-------------+-----------+----------+------+
| Accounting | Shelley | Higgins | 12008.00 | 1 |
| Accounting | William | Gietz | 8300.00 | 2 |
| Administration | Jennifer | Whalen | 4400.00 | 1 |
| Benefits | NULL | NULL | NULL | NULL |
| Construction | NULL | NULL | NULL | NULL |
| Contracting | NULL | NULL | NULL | NULL |
| Control And Credit | NULL | NULL | NULL | NULL |
| Corporate Tax | NULL | NULL | NULL | NULL |
| Executive | Steven | King | 24000.00 | 1 |
| Executive | Neena | Kochhar | 17000.00 | 2 |
| Executive | Lex | De Haan | 17000.00 | 3 |
| Finance | Nancy | Greenberg | 12008.00 | 1 |
| Finance | Daniel | Faviet | 9000.00 | 2 |
| Finance | John | Chen | 8200.00 | 3 |
| Finance | Jose Manuel | Urman | 7800.00 | 4 |
| Finance | Ismael | Sciarra | 7700.00 | 5 |
| Government Sales | NULL | NULL | NULL | NULL |
| Human Resources | Susan | Mavris | 6500.00 | 1 |
...
51 rows in set (0.00 sec)
以上語句基于前面的一個失敗示例,只是在LEFT JOIN之后,派生表之前加上一個LATERAL關鍵字,使得子查詢 t 能夠引用前面的 departments 表。這種方法能夠?qū)崿F(xiàn)很多類似的復雜功能,簡單而且高效。
MySQL 從 8.0.14 開始支持橫向派生表,同時存在以下限制:
橫向派生表只能出現(xiàn)在FROM子句中,包括使用逗號分隔的表或者標準的連接語句(JOIN、INNER JOIN、CROSS JOIN、LEFT [OUTER] JOIN以及RIGHT [OUTER] JOIN)。
如果橫向派生表位于連接操作的右側(cè),并且引用了左側(cè)的表,連接類型必須為INNER JOIN、CROSS JOIN或者LEFT [OUTER] JOIN。
如果橫向派生表位于連接操作的左側(cè),并且引用了右側(cè)的表,連接類型必須為INNER JOIN、CROSS JOIN或者RIGHT [OUTER] JOIN。
如果橫向派生表引用了聚合函數(shù),那么該函數(shù)的聚合查詢語句不能是橫向派生表所在的FROM子句所屬的查詢語句。
根據(jù) SQL 標準,表函數(shù)擁有一個隱式的LATERAL,這與 MySQL 8.0 到 MySQL 8.0.14 之前版本的實現(xiàn)一致。但是,根據(jù)標準,函數(shù) JSON_TABLE() 之前不能存在LATERAL關鍵字,包括隱式的LATERAL。
當然,也可以使用 MySQL 8.0 中新增的窗口函數(shù)完成相同的功能:
SELECT *
FROM (SELECT d.department_name, e.first_name, e.last_name, e.salary,
row_number() OVER (PARTITION BY d.department_id ORDER BY e.salary DESC) AS rn
FROM departments d
LEFT JOIN employees e
ON (e.department_id = d.department_id)
) AS t
WHERE t.rn <= 5
ORDER BY t.department_name,t.rn;
+----------------------+-------------+-----------+----------+----+
| department_name | first_name | last_name | salary | rn |
+----------------------+-------------+-----------+----------+----+
| Accounting | Shelley | Higgins | 12008.00 | 1 |
| Accounting | William | Gietz | 8300.00 | 2 |
| Administration | Jennifer | Whalen | 4400.00 | 1 |
| Benefits | NULL | NULL | NULL | 1 |
| Construction | NULL | NULL | NULL | 1 |
| Contracting | NULL | NULL | NULL | 1 |
| Control And Credit | NULL | NULL | NULL | 1 |
| Corporate Tax | NULL | NULL | NULL | 1 |
| Executive | Steven | King | 24000.00 | 1 |
| Executive | Neena | Kochhar | 17000.00 | 2 |
| Executive | Lex | De Haan | 17000.00 | 3 |
| Finance | Nancy | Greenberg | 12008.00 | 1 |
| Finance | Daniel | Faviet | 9000.00 | 2 |
| Finance | John | Chen | 8200.00 | 3 |
| Finance | Jose Manuel | Urman | 7800.00 | 4 |
| Finance | Ismael | Sciarra | 7700.00 | 5 |
| Government Sales | NULL | NULL | NULL | 1 |
| Human Resources | Susan | Mavris | 6500.00 | 1 |
...
51 rows in set (0.00 sec)
相關文檔:
MySQL 8.0 橫向派生表
其他數(shù)據(jù)庫產(chǎn)品的功能實現(xiàn):
Oracle 18c 橫向(LATERAL)內(nèi)聯(lián)視圖
Oracle 18c APPLY 連接
SQL Server 2017 APPLY 運算符
PostgreSQL 11 橫向(LATERAL)子查詢
Db2 11 橫向(LATERAL)連接