SQL 查詢是從 Select 開始的嗎?
在下方公眾號后臺回復:面試手冊,可獲取杰哥匯總的 3 份面試 PDF 手冊。
好吧,顯然很多SQL查詢都是從SELECT開始的(實際上本文只是關注SELECT查詢,而不是INSERT或其它別的什么)。
但是!昨天我正在做窗口函數(shù)的解釋說明,并且我發(fā)現(xiàn)自己在谷歌上搜索“你能根據(jù)窗口函數(shù)的結果進行過濾嗎”。比如 — 你能在WHERE、HAVING或者其它地方過濾窗口函數(shù)的結果嗎?
最后我得出的結論是:“窗口函數(shù)必須在WHERE和GROUP BY之后運行,所以你做不到”。但這讓我想到了一個更大的問題 — SQL查詢的實際運行順序是什么?
這是我憑直覺就知道的事情(“我肯定知道!我已經(jīng)編寫了至少10000個SQL查詢,其中一些非常復雜!),但我很難真正地準確說出順序是什么。
1、SQL查詢按此順序進行
這就是我查找到的順序!(SELECT并不是在第一步執(zhí)行,而是到第五步才執(zhí)行)
(這里是一篇推特:https://twitter.com/b0rk/status/1179449535938076673)
(我真的很想找到一種比“sql查詢按此順序發(fā)生/運行”更準確的表達方式,但我還沒想出來。)

在非圖形格式中,其順序為:
l FROM/JOIN 和所有的 ON 條件
l WHERE
l GROUP BY
l HAVING
l SELECT(包括窗口函數(shù))
l ORDER BY
l LIMIT
2、圖解此圖有助于你做出回答
此圖是關于SQL查詢的語義的 — 你可以通過它,對給定查詢將返回什么結果進行推理,并回答如下問題:
我能在一個GROUP BY的結果上執(zhí)行WHERE么?(不行!WHERE發(fā)生在GROUP BY之前!)
我可以根據(jù)窗口函數(shù)的結果進行過濾嗎(不行!窗口函數(shù)發(fā)生在SELECT中,它發(fā)生在WHERE和GROUP BY之后)
我可以基于GROUP BY中所做的來進行ORDER BY么?(可以!ORDER BY是最后執(zhí)行的基本步驟,你可以根據(jù)任何東西做ORDER BY!)
LIMIT何時執(zhí)行?(在最后!)
3、數(shù)據(jù)庫引擎實際并不是按這個順序運行查詢
因為它實現(xiàn)了一系列優(yōu)化以使查詢運行得更快 — 我們稍后將在本文中討論這一點。
所以:
當你只想了解哪些查詢是有效的,以及如何推理給定查詢的結果時,可以使用此圖。
你不應該使用此圖來解釋查詢性能或任何有關索引的事情,那是一個復雜得多的問題,涉及更多變量。
4、混淆因素:列別名
有人在Twitter上指出,許多SQL實現(xiàn)允許你使用以下語法:
SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)
FROM table
GROUP BY full_name
此查詢使其看起來像是在SELECT之后才發(fā)生GROUP BY,即使GROUP BY先執(zhí)行,因為GROUP BY引用了SELECT中的別名。但是要使GROUP BY發(fā)揮作用,其實并不需要在SELECT之后才運行 — 數(shù)據(jù)庫引擎只要將查詢重寫為:
SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)
FROM table
GROUP BY CONCAT(first_name, ' ', last_name)
并且首先運行GROUP BY。
你的數(shù)據(jù)庫引擎肯定還會在開始運行查詢之前執(zhí)行一系列檢查,確保你在SELECT和GROUP BY中放置的內容合在一起是有意義的,因此在開始制定執(zhí)行計劃之前,它必須將查詢作為一個整體來查看。
5、查詢實際上不是按此順序運行的(優(yōu)化!)
實際上,數(shù)據(jù)庫引擎并不是真的通過連接、然后過濾、然后再分組來運行查詢,因為它們實現(xiàn)了一系列優(yōu)化,只要重新排列執(zhí)行順序不改變查詢結果,就可以重排以使查詢運行得更快。
一個簡單的例子說明了為什么需要以不同的順序運行查詢以使其快速運行,在這個查詢中:
SELECT * FROM
owners LEFT JOIN cats ON owners.id = cats.owner
WHERE cats.name = 'mr darcy'
如果你只需要查找3個名為“mr darcy”的貓,那么執(zhí)行整個左連接并匹配這兩個表中的所有行是非常愚蠢的 —— 首先對名為“mr darcy”的貓進行一些篩選要快得多。在這種情況下,先進行過濾不會改變查詢結果!
實際上,數(shù)據(jù)庫引擎還實現(xiàn)了許多其它優(yōu)化,這些優(yōu)化可能會使它們以不同的順序運行查詢,但不能再說了,老實講,這方面我不是專家。
6、LINQ以FROM開始查詢
LINQ(一種C#和VB.NET中的查詢語法)使用的順序為FROM … WHERE … SELECT。下面是一個LINQ查詢的示例:
var teenAgerStudent = from s in studentList
where s.Age > 12 && s.Age < 20
select s;
pandas(我所喜歡的數(shù)據(jù)治理工具:https://github.com/jvns/pandas-cookbook)也基本上是這樣工作的,盡管你不需要使用這種精確的順序 — 我經(jīng)常會這樣編寫pandas代碼:
df = thing1.join(thing2) # like a JOIN
df = df[df.created_at > 1000] # like a WHERE
df = df.groupby('something', num_yes = ('yes', 'sum')) # like a GROUP BY
df = df[df.num_yes > 2] # like a HAVING, filtering on the result of a GROUP BY
df = df[['num_yes', 'something1', 'something']] # pick the columns I want to display, like a SELECT
df.sort_values('sometthing', ascending=True)[:30] # ORDER BY and LIMIT
df[:30]
這并不是因為pandas對如何編寫代碼強加了任何特定規(guī)則。只是按照JOIN / WHERE / GROUP BY / HAVING的順序編寫代碼通常好理解。(不過,我經(jīng)常會先放一個WHERE來提高性能,而且我認為大多數(shù)數(shù)據(jù)庫引擎實際也會先執(zhí)行WHERE)
在R的dplyr中,你還能使用不同的語法來查詢諸如Postgres、MySQL或SQLite等SQL數(shù)據(jù)庫,它們的順序也更符合邏輯。
不知道這一點令我自己著實驚訝
我寫了這樣一篇博文,因為當我發(fā)現(xiàn)這個順序的時候非常驚訝,我以前從來沒有看到過它被這樣寫下來 — 它基本上解釋了我憑直覺所知道的,關于為什么一些查詢被允許而另一些不被允許的一切。所以我想把它寫下來,希望它能幫助其他人理解如何編寫SQL查詢。
作者 | Julia Evans
譯者 | 王雪迎 責編 | 孫勝
出品 | CSDN(ID:CSDNnews)
原文:https://jvns.ca/blog/2019/10/03/sql-queries-don-t-start-with-select/
推薦閱讀

