SQL 查詢并不是從 Select 開始的
本文轉(zhuǎn)載自微信公眾號「HelloGitHub」,作者HelloGitHub。轉(zhuǎn)載本文請聯(lián)系HelloGitHub公眾號。
日常使用中寫 SQL 查詢命令都是以 SELECT 開始的(注意:本文僅探討 SELECT 查詢,不涵蓋 insert 或其他 SQL 命令)。
昨天我想到一個問題:可以用 WHERE、HAVING 或者其他方式來過濾窗口函數(shù)執(zhí)行結(jié)果嗎?
經(jīng)過一番探索,我得出的最終結(jié)論是否定的,因為窗口函數(shù)必須在 WHERE 和 GROUP BY 之后才能運行。但是,這也延伸到了一個更大的問題——SQL 查詢的執(zhí)行順序是怎么樣的呢?
SQL 查詢執(zhí)行順序
我專門查了一下文檔 SQL 查詢執(zhí)行順序如下:
如果不喜歡以上五彩斑斕的圖片形式,也可以看下面的文字:
- FROM/JOIN/ON
- WHERE
- GROUP BY
- HAVING
- SELECT(窗口函數(shù)即在此步驟執(zhí)行)
- ORDER BY
- LIMIT
上圖可以解答你的如下疑惑:
上圖是 SQL 查詢的語義說明??炊@張圖,便能迅速判斷一個給定的 SQL 查詢將會返回什么結(jié)果,也可以輕松解答如下疑問:
- 可以對 GROUP BY 的結(jié)果進行WHERE 篩選嗎?(不可以!因為 WHERE 在 GROUP BY 之前執(zhí)行)
- 可以對窗口函數(shù)的執(zhí)行結(jié)果進行過濾嗎?(不可以!因為窗口函數(shù)在 SELECT 步驟執(zhí)行,而這步是在 WHERE 和 GROUP BY 之后)
- 可以對 GROUP BY 的結(jié)果再執(zhí)行 ORDER BY 操作嗎? (可以!ORDER BY 基本上是最后一個步驟了,所以可以對任何操作的執(zhí)行結(jié)果執(zhí)行 ORDER BY)
- LIMIT 執(zhí)行在哪個步驟? (最后一步!)
雖然如此,但實際上數(shù)據(jù)庫引擎并非嚴(yán)格按照這個順序運行查詢,因為它們還會執(zhí)行一系列的優(yōu)化,以便提升查詢速度。
所以:
- 當(dāng)你想了解查詢語句的有效性,或是想搞明白為什么會返回這樣一個查詢結(jié)果時,可以嘗試用該圖來解釋;
- 但是,使用該圖是無法解釋查詢性能或索引相關(guān)問題的,它們會涉及到更多變量,因而也更為復(fù)雜。
一、最容易搞混的:列別名
比如:關(guān)聯(lián)姓和名,并對其進行分組。SQL 語法是允許這樣寫:
- SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)
- FROM table
- GROUP BY full_name
上面的查詢看起來像是在 SELECT 之后執(zhí)行 GROUP BY,但其實 GROUP BY是先執(zhí)行的,因為 GROUP BY 引用了 SELECT 中的 alias。
數(shù)據(jù)庫引擎是可以將查詢重寫為:
- SELECT * FROM
- owners LEFT JOIN cats ON owners.id = cats.owner
- WHERE cats.name = 'mr darcy'
接著,先執(zhí)行 GROUP BY 中的語句,再進行 SELECT 操作,所以上面那么寫是可行的。
此外,數(shù)據(jù)庫引擎肯定會執(zhí)行一系列檢查,以確保在查詢開始運行之前,SELECT 和 GROUP BY 中的內(nèi)容相匹配,因此在制定執(zhí)行計劃之前,它必須將查詢語句當(dāng)作一個整體來檢查。
二、查詢并非嚴(yán)格按照此順序運行(優(yōu)化)
實際上,數(shù)據(jù)庫引擎并不是通過連接、過濾和分組來運行查詢,因為它實現(xiàn)了一系列優(yōu)化來提升查詢速度,如重新排序(只要不影響最終返回結(jié)果)。
這里列舉一個簡單的例子來說明查詢的執(zhí)行順序是如何影響了查詢性能。
- SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)
- FROM table
- GROUP BY full_name
如果只需要查找 3 個名為“mr darcy”的貓,那么執(zhí)行整個左連接并匹配這兩個表中的所有行是很慢的。相反,如果先對名為“mr darcy”的貓進行篩選再去執(zhí)行連接,則要快得多。在這種情況下,先執(zhí)行過濾不會改變查詢的結(jié)果!
實際上,數(shù)據(jù)庫引擎還實現(xiàn)了許多其他的優(yōu)化,使得查詢語句以另外的順序來執(zhí)行,這里暫且不表。
三、不一樣的查詢語法
LINQ(C# 和 VB.NET 中的查詢語法)是按照FROM ... WHERE ... SELECT的順序來執(zhí)行查詢。以下是 LINQ 查詢的示例:
- var teenAgerStudent = from s in studentList
- where s.Age > 12 && s.Age < 20
- select s;
Pandas(Python 數(shù)據(jù)統(tǒng)計分析工具)也基本上是這樣工作的,盡管有時候不需要嚴(yán)格按照下面的順序來編寫代碼,但這樣也不失為一種好習(xí)慣:
- 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 的順序來編寫代碼更有助于理解底層邏輯。(值得一提的是,可以在 JOIN 之前先執(zhí)行 WHERE 來提高性能,大多數(shù)數(shù)據(jù)庫引擎在實踐中也是這樣來執(zhí)行的)
R 中的 dplyr(R 語言用來操作數(shù)據(jù)框的包)還允許采用不同的語法查詢不同的 SQL 數(shù)據(jù)庫,如:Postgres、MySQL 和 SQLite。
最后
當(dāng)我發(fā)現(xiàn) SQL 查詢語句的這種執(zhí)行順序時,我其實是非常驚訝的。通過探究 SQL 查詢語句的執(zhí)行順序,把我之前遇到的問題搞清楚了。也希望本文能幫助到更多的人理解 SQL 的執(zhí)行順序以及如何正確編寫 SQL 查詢語句。
最后,感謝作者的授權(quán):
原文地址:SQL queries don't start with SELECT
原文作者:Julia Evans(已授權(quán))
譯者 & 校正:HelloGitHub-小熊熊 & 鹵蛋