SQL查詢並不總是以SELECT開始

  • 2019 年 11 月 26 日
  • 筆記

很多 SQL 查詢確實以 SELECT 開始(本文僅涉及 SELECT 查詢,而不涉及 INSERT 或其他內容)。不過,我在網上搜索 『是否可以對窗口函數返回的結果進行過濾』 這個問題,或者說可以在 WHERE、HAVING 或其他中過濾窗口函數的結果嗎?最終我得出的結論是:窗口函數必須在 WHERE 和 GROUP BY 發生之後才能運行,所以答案是我們這樣做。於是又引出了另一個問題:SQL 查詢的執行順序是什麼樣的?

直覺上這個問題應該很好回答,畢竟我自己已經至少寫了 10000 個 SQL 查詢了,其中還有一些是很複雜。但事實是,我仍然很難準確地說出它的執行順序是什麼樣的。

1. SQL查詢按此順序發生

我研究了一下,執行順序如下所示。SELECT 並不是第一個執行的,而是第五個。

執行順序如下:

  • FROM/JOIN 以及所有 ON 表達式
  • WHERE
  • GROUP BY
  • HAVING
  • SELECT (包括窗口函數)
  • ORDER BY
  • LIMIT

2. 這張圖可幫助我們回答以下問題

這張圖與 SQL 查詢語義相關,讓我們可以推理出給定查詢返回的內容,並回答如下問題:

  • 可以在 GRROUP BY 之後使用 WHERE 嗎?(不行,WHERE 是在 GROUP BY 之後使用!)
  • 可以對窗口函數返回的結果進行過濾嗎?(不行,窗口函數發生在 SELECT 語句中,而 SELECT 發生在 WHERE 和 GROUP BY 之後)
  • 可以對 GROUP BY 里的東西進行 ORDER BY 嗎?(可以,ORDER BY 基本在最後執行,所以可以對任何東西進行 ORDER BY)
  • LIMIT 發生在什麼時候?(發生在最後!)

實際上,資料庫引擎並不一定按照這個順序執行查詢,因為為了使查詢運行更快,實現了一系列優化。所以:

  • 當我們只想了解哪些查詢是合法的以及如何推理給定查詢的返回結果時,可以參考上圖。
  • 當我們在推斷查詢性能或者包含索引的任何東西時,上圖就不適用了。

3. 混合因素:列別名

Twitter上的有人指出,許多 SQL 可以使用如下語法實現:

SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)  FROM table  GROUP BY full_name

上面的查詢看起來 GROUP BY 發生在 SELECT 之後,因為 GROUP BY 引用了 SELECT 中的一個別名。實際上並不需要讓 GROUP BY 發生在 SELECT 之後,因為資料庫引擎可以將查詢重寫為:

SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)  FROM table  GROUP BY CONCAT(first_name, ' ', last_name)

這樣 GROUP BY 仍然會先執行。

我們的資料庫引擎也會進行一系列的檢查,以確保在運行查詢之前,我們在 SELECT 和 GROUP BY 中輸入的內容是合法的,因此在生成執行計劃之前必須從整體上檢查一下查詢。

4. 查詢可能不會按上述順序運行

實際上,資料庫引擎並不一定會按照 JOIN、WHERE、GROUP BY 的順序來執行查詢,因為它們會進行一系列優化,只要重新排序不會改變查詢的結果,它們就會對命令進行重新排序以使查詢運行得更快。

下面這個簡單的示例說明了為什麼需要以不同的順序運行查詢以使其快速運行:

SELECT *  FROM owners  LEFT JOIN cats  ON owners.id = cats.owner  WHERE cats.name = 'mr darcy'

按照上圖執行順序我們知道:FROM / LEFT JOIN / ON 會先執行,然後是 WHERE, 最後是 SELECT。

如果只需要查找名為』mr darcy』的貓,那就沒必要對兩張表的所有行進行左連接,先對貓名為 『mr darcy』 執行過濾會更快。在這種情況下,先執行過濾不會改變查詢結果!

在實踐中,資料庫引擎還會有很多其他優化措施,這些優化措施可能會使它們以不同的順序執行查詢,因為我不是這方面的專家,所以在這不展開介紹。

原文:SQL queries don』t start with SELECT