1. 程式人生 > >技術分享 | 派生表中的各種外部引用

技術分享 | 派生表中的各種外部引用

原創: 管長龍 譯 愛可生開源社群 昨天

作者:Guilhem Bichot 翻譯:管長龍

 

使用 LATERAL,JOIN 可以具有第二個表 - 基於子查詢的派生表 - 基於第一個表的列的值進行定義,因此可以為第一個表的每一行重新計算。典型:

SELECT ... FROM t1, LATERAL (SELECT ... FROM t2
^ WHERE t2.col=t1.col ... ) AS derived;
| |
| |
+---------------------------+

在第二個表(派生的)中,t1.col

 是第一個表 t1 的“橫向外部引用”。引用的表被放置在“派生表”的“旁邊”(即兩者都是同一 FROM 子句的一部分)。

在實現此 LATERAL 功能時,我同時添加了另一個相關功能:支援派生表中的非橫向外部引用。

分層資料示例:

CREATE TABLE employees (
id INT PRIMARY KEY NOT NULL,
name VARCHAR(100) NOT NULL,
manager_id INT NULL,
INDEX (manager_id),
FOREIGN KEY (manager_id) REFERENCES employees (id)
);

INSERT INTO employees VALUES
(333, "Yasmina", NULL), # Yasmina is the CEO (manager_id is NULL)
(198, "John", 333), # John has ID 198 and reports to 333 (Yasmina)
(692, "Tarek", 333),
(29, "Pedro", 198),
(4610, "Sarah", 29),
(72, "Pierre", 29),
(123, "Adil", 692);

每個人接收到直接和間接報告的數量?此過程包含 MySQL 遞迴語法

SELECT emp.*,
(
WITH RECURSIVE reports AS
(
SELECT emp.id
UNION ALL
SELECT e.id
FROM reports AS rep JOIN employees AS e
ON rep.id = e.manager_id
)
SELECT COUNT(*)-1 FROM reports # 每次計算返回的統計結果
) AS count_of_all_reports
FROM employees AS emp;

描述:對於每位員工:

  • 評估一個標量子查詢(第 2-12 行)count_of_all_reports,其中:

  • 通過遞迴查詢員工的所有直接和間接報告來構建 CTE(第 3-10 行)

  • 計算 CTE 的行數(第 11 行),減去一行不計算員工

  • 返回計數。

CTE 意為共用表示式(Common Table Expression),通常用於構建複雜查詢。

結果:

+------+---------+------------+----------------------+
| id | name | manager_id | count_of_all_reports |
+------+---------+------------+----------------------+
| 29 | Pedro | 198 | 2 |
| 72 | Pierre | 29 | 0 |
| 123 | Adil | 692 | 0 |
| 198 | John | 333 | 3 |
| 333 | Yasmina | NULL | 6 |
| 692 | Tarek | 333 | 1 |
| 4610 | Sarah | 29 | 0 |
+------+---------+------------+----------------------+
7 rows in set (0.02 sec)

CTE 的解釋:從 SELECT emp.id 開始遞迴,這是對我們想要計算的當前員工的引用;這個emp.id 來自於其中一行 emp (CTE 之外)。

如果我們從“引用”到“引用列”繪製一個箭頭,則此箭頭從 CTE 開始,遍歷到邊界,再遍歷到周圍的標量子查詢的邊界,並最終到達頂部查詢。這就是為什麼它不是“橫向外部引用”。

SELECT emp.*,
(
WITH RECURSIVE reports AS
( +----------------------------------+
| |
SELECT emp.id |
UNION ALL |
SELECT e.id |
FROM reports AS rep JOIN employees AS e |
ON rep.id = e.manager_id |
) | crosses CTE's bounds
SELECT COUNT(*)-1 FROM reports |
) AS count_of_all_reports | crosses scalar subquery's bounds
FROM employees AS emp; |
^ |
| |
+-----------------------------+ reaches to farthest outside

在 MySQL 8.0.14 之前,這是不可能的(MySQL 在 CTE 的定義中不知道 emp.id 是什麼)。

新版本 MySQL 檢測到這個引用;它得出結論,必須為 emp.id 的每一行重新計算 標量子查詢 及其包含的 CTE。

檢視 EXPLAIN 查詢:

+----+--------------------+------------+------------+------+---------------+------------+---------+--------+------+----------+------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+------------+------------+------+---------------+------------+---------+--------+------+----------+------------------------+
| 1 | PRIMARY | emp | NULL | ALL | NULL | NULL | NULL | NULL | 7 | 100.00 | NULL |
| 2 | DEPENDENT SUBQUERY | | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL |
| 3 | DEPENDENT DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables
used |
| 4 | UNCACHEABLE UNION | rep | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | Recursive; Using where |
| 4 | UNCACHEABLE UNION | e | NULL | ref | manager_id | manager_id | 5 | rep.id | 1 | 100.00 | Using index |
+----+--------------------+------------+------------+------+---------------+------------+---------+--------+------+----------+------------------------+

我們看到 MySQL 已經認識到 標量子查詢 是“依賴的”(取決於外部資料),對於派生表也是如此。它還看到 CTE 中 UNION 的內容是“不可快取的”,每次都必須重新計算。

回顧一下,從 MySQL 8.0.14 開始:

  • 預設情況下,在解析派生表的定義時,MySQL 接受非橫向外部引用,如上面的示例查詢中所示。

  • 如果你新增 LATERAL 關鍵字,MySQL 也接受橫向外部引用;換句話說,它還在包含派生表的 FROM 子句中進行搜尋。

注意:報告計數問題還有其他解決方案。一種解決方案是使用一個遞迴 CTE 在一次傳遞中構建一個大結果,列出所有員工和每個間接管理器之間的所有連線,然後使用這個大的結果來聚合每個經理。它有效,但很難閱讀。相反,我們上面所做的是從層次結構中逐個生成較小的集合。所以它是“走層次 / 聚合 / 重複的一部分”而不是“走整個