一般來說,SQL 子查詢只能引用外查詢中的字段,而不能使用同一層級(jí)中其他表中的字段。例如:
-- 錯(cuò)誤示例
SELECT d.dept_name,
t.avg_salary
FROM department d
JOIN (SELECT avg(e.salary) AS avg_salary
FROM employee e
WHERE e.dept_id = d.dept_id) t;
SQL 錯(cuò)誤 [42601]: ERROR: syntax error at end of input
位置:183
由于 JOIN 子句中的查詢語(yǔ)句 t 引用了左側(cè) department 表中的字段,因此產(chǎn)生了語(yǔ)法錯(cuò)誤。
為了解決以上問題,我們可以使用 PostgreSQL 提供的橫向子查詢(LATERAL subquery)。不過在介紹 LATERAL 關(guān)鍵字之前,我們先來回顧一下 SELECT 和 FROM 子句的含義。例如:
SELECT dept_id, dept_name
FROM department;
簡(jiǎn)單來說,我們可以將以上查詢看作一個(gè)循環(huán)處理語(yǔ)句。使用偽代碼實(shí)現(xiàn)的以上 SQL 語(yǔ)句如下:
for dept_id, dept_name in department
loop
print dept_id, dept_name
end loop
對(duì)于 department 中的每一條記錄,都執(zhí)行 SELECT 語(yǔ)句指定的操作,以上示例簡(jiǎn)單的輸出了每行記錄。
SELECT 就像一個(gè)循環(huán)語(yǔ)句,而 LATERAL 就像是一個(gè)嵌套循環(huán)語(yǔ)句,對(duì)于左側(cè)表中的每行記錄執(zhí)行一次子查詢操作。例如,通過增加 LATERAL 關(guān)鍵字,我們可以修改第一個(gè)示例:
SELECT d.dept_name,
t.avg_salary
FROM department d
CROSS JOIN LATERAL
(SELECT avg(e.salary) AS avg_salary
FROM employee e
WHERE e.dept_id = d.dept_id) t;
dept_name |avg_salary |
-----------+----------------------+
行政管理部 | 26666.666666666667|
人力資源部 |13166.6666666666666667|
財(cái)務(wù)部 | 9000.0000000000000000|
研發(fā)部 | 7577.7777777777777778|
銷售部 | 5012.5000000000000000|
保衛(wèi)部 | |
CROSS JOIN LATERAL 右側(cè)的查詢可以引用左側(cè)表中的字段,以上語(yǔ)句為 JOIN 左側(cè)的每個(gè)部門返回了月薪總和。
LATERAL 可以幫助我們實(shí)現(xiàn)一些有用的分析功能,例如以下查詢返回了每個(gè)部門月薪最高的 3 名員工:
SELECT d.dept_name, t.emp_name, t.salary
FROM department d
LEFT JOIN LATERAL
?。⊿ELECT emp_name, salary
FROM employee e
WHERE e.dept_id = d.dept_id
ORDER BY salary DESC
LIMIT 3) t
ON TRUE;
dept_name |emp_name|salary |
-----------+--------+--------+
行政管理部 |劉備 |30000.00|
行政管理部 |關(guān)羽 |26000.00|
行政管理部 |張飛 |24000.00|
人力資源部 |諸葛亮 |24000.00|
人力資源部 |黃忠 | 8000.00|
人力資源部 |魏延 | 7500.00|
財(cái)務(wù)部 |孫尚香 |12000.00|
財(cái)務(wù)部 |孫丫鬟 | 6000.00|
研發(fā)部 |趙云 |15000.00|
研發(fā)部 |周倉(cāng) | 8000.00|
研發(fā)部 |關(guān)興 | 7000.00|
銷售部 |法正 |10000.00|
銷售部 |簡(jiǎn)雍 | 4800.00|
銷售部 |孫乾 | 4700.00|
保衛(wèi)部 | | |
對(duì)于 department 中的每個(gè)部門,子查詢 t 最多返回 3 個(gè)員工信息。我們使用了 LEFT JOIN LATERAL,從而保證了“保衛(wèi)部”也會(huì)返回一條數(shù)據(jù)。
同樣使用偽代碼表示以上查詢語(yǔ)句:
for d in department
loop
for e in employee order by salary desc
loop
cnt++
if cnt <= 3
then
return e
else
goto next d
end
end loop
end loop
通過 EXPLIAN 命令查看以上語(yǔ)句的執(zhí)行計(jì)劃:
EXPLAIN
SELECT d.dept_name, t.emp_name, t.salary
FROM department d
LEFT JOIN LATERAL
(SELECT emp_name, salary
FROM employee e
WHERE e.dept_id = d.dept_id
ORDER BY salary DESC
LIMIT 3) t
ON TRUE;
QUERY PLAN |
-------------------------------------------------------------------------------------------------+
Nested Loop Left Join (cost=8.174439.35 rows=540 width=250) |
-> Seq Scan on department d (cost=0.0015.40 rows=540 width=122) |
-> Limit (cost=8.178.17 rows=1 width=132) |
-> Sort (cost=8.178.17 rows=1 width=132) |
Sort Key: e.salary DESC |
-> Index Scan using idx_emp_dept on employee e (cost=0.148.16 rows=1 width=132)|
Index Cond: (dept_id = d.dept_id) |
Nested Loop Left Join 說明 PostgreSQL 使用的就是嵌套循環(huán)算法。