好的,我們來詳細講解一下 SQL 查詢語句的執行順序。
很多人會誤以為 SQL 的執行順序就是我們寫的順序(SELECT
-> FROM
-> WHERE
-> GROUP BY
-> HAVING
-> ORDER BY
),但實際上,數據庫引擎在底層處理查詢時,遵循一個完全不同的邏輯順序。理解這個順序對于編寫高效、正確的 SQL 查詢至關重要。
一、核心執行順序(邏輯查詢處理順序)
這是 SQL 查詢在數據庫內部被處理的邏輯步驟。每一步都會產生一個虛擬表,作為下一步的輸入。
我們以一個完整的查詢為例:
SELECT DISTINCT column1, column2
FROM table1
JOIN table2 ON table1.id = table2.id
WHERE condition
GROUP BY column1
HAVING group_condition
ORDER BY column2 DESC
LIMIT n;
它的實際執行順序如下:
第 1 步:FROM
/ JOIN
- 作用:確定需要查詢的數據來源。數據庫引擎首先會執行
FROM
子句,如果有關聯的表(JOIN
),它會根據ON
條件將這些表連接起來,形成一個大的虛擬表(VT1)。 - 為什么是第一步:因為所有后續的操作(篩選、分組、排序)都必須基于一個完整的數據集。
第 2 步:WHERE
- 作用:對
FROM
步驟生成的虛擬表(VT1)進行行級過濾。根據WHERE
子句中的條件,移除不滿足條件的行,生成一個新的虛擬表(VT2)。 - 關鍵點:
WHERE
子句在分組和聚合之前執行。因此,它不能使用聚合函數(如COUNT()
,SUM()
,AVG()
)。如果你嘗試在WHERE
中使用聚合函數,數據庫會報錯。 - 性能提示:這是進行數據篩選最重要的環節,盡早過濾掉無用數據可以大大減少后續步驟的計算量。
第 3 步:GROUP BY
- 作用:根據
GROUP BY
子句中指定的列,將WHERE
步驟過濾后的虛擬表(VT2)中的數據進行分組。具有相同分組列值的行會被合并到一組,生成一個新的虛擬表(VT3)。 - 結果:此時的虛擬表由多個“組”構成,每一組代表一個唯一的分組鍵值。
第 4 步:HAVING
- 作用:對
GROUP BY
步驟生成的分組進行過濾。它類似于WHERE
,但作用于組而不是單行。根據HAVING
子句中的條件,移除不滿足條件的組,生成一個新的虛擬表(VT4)。 - 關鍵點:
HAVING
在分組之后執行,因此它可以使用聚合函數(如HAVING COUNT(*) > 5
)。這是HAVING
和WHERE
最本質的區別。
第 5 步:SELECT
- 作用:這是最容易被誤解的一步。直到這一步,數據庫才開始真正“選擇”需要返回的列。它會遍歷
HAVING
步驟生成的虛擬表(VT4),并只保留SELECT
子句中明確指定的列(或表達式),生成一個新的虛擬表(VT5)。 - 關鍵點:
- 別名生效:在
SELECT
步驟中定義的列別名(如SELECT salary * 12 AS annual_salary
),在這一步之后才生效。這就是為什么在WHERE
或GROUP BY
中不能使用SELECT
中定義的別名,但在ORDER BY
中卻可以的原因。 - 表達式計算:在
SELECT
中定義的表達式(如數學運算、函數調用)也是在這一步進行計算的。
- 別名生效:在
第 6 步:DISTINCT
- 作用:對
SELECT
步驟生成的虛擬表(VT5)進行去重。移除所有完全相同的行,生成一個新的虛擬表(VT6)。 - 性能:
DISTINCT
操作通常需要排序或哈希,可能會消耗較多資源,應謹慎使用。
第 7 步:ORDER BY
- 作用:對最終的結果集(
DISTINCT
步驟后的虛擬表 VT6)進行排序。根據ORDER BY
子句中指定的列和排序方式(ASC
或DESC
)對行進行排序。 - 關鍵點:
- 最后一步之一:
ORDER BY
是在幾乎所有數據處理完成后才執行的。 - 可以使用別名:因為
ORDER BY
在SELECT
之后執行,所以它可以引用SELECT
中定義的列別名。
- 最后一步之一:
第 8 步:LIMIT
/ OFFSET
/ TOP
- 作用:這是整個查詢的最后一步。它從排序好的結果集中,限制返回的行數。
LIMIT n
:返回前 n 行。OFFSET m LIMIT n
:跳過前 m 行,返回接下來的 n 行。TOP n
:返回前 n 行。
- 性能提示:
LIMIT
通常在分頁查詢中使用。但請注意,如果查詢中包含了ORDER BY
,數據庫需要先對所有符合條件的數據進行排序,然后再應用LIMIT
,這在數據量很大時可能會很慢。
二、執行順序與書寫順序的對比
執行順序 | 子句 | 作用描述 | 書寫順序 |
---|---|---|---|
1 | FROM , JOIN | 確定數據源,連接表 | 2 |
2 | WHERE | 過濾行(在分組前) | 3 |
3 | GROUP BY | 對行進行分組 | 4 |
4 | HAVING | 過濾組(在分組后) | 5 |
5 | SELECT | 選擇列,計算表達式,定義別名 | 1 |
6 | DISTINCT | 去重 | 1 (在SELECT后) |
7 | ORDER BY | 對最終結果排序 | 6 |
8 | LIMIT | 限制返回的行數 | 7 |
三、為什么理解執行順序很重要?(實例說明)
1. 為什么 WHERE
中不能用聚合函數,而 HAVING
可以?
-- 錯誤的寫法
SELECT department, COUNT(*) as emp_count
FROM employees
WHERE COUNT(*) > 5 -- 報錯!因為 WHERE 在 GROUP BY 之前執行,此時 COUNT(*) 還不存在
GROUP BY department;
-- 正確的寫法
SELECT department, COUNT(*) as emp_count
FROM employees
GROUP BY department -- 先分組
HAVING COUNT(*) > 5; -- 再對組進行過濾
2. 為什么 ORDER BY
可以使用 SELECT
中的別名,而 WHERE
不可以?
-- 錯誤的寫法
SELECT first_name, salary * 12 AS annual_salary
FROM employees
WHERE annual_salary > 60000; -- 報錯!因為 WHERE 在 SELECT 之前執行,別名 'annual_salary' 還不存在
-- 正確的寫法
SELECT first_name, salary * 12 AS annual_salary
FROM employees
WHERE salary * 12 > 60000; -- 在 WHERE 中重復寫表達式
-- 或者,利用執行順序,在 ORDER BY 中使用別名
SELECT first_name, salary * 12 AS annual_salary
FROM employees
WHERE salary > 5000 -- 先用原始列過濾
ORDER BY annual_salary DESC; -- 再用別名排序(因為 ORDER BY 在 SELECT 之后)
總結
記住這個核心順序:FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT。
將 SQL 的執行順序想象成一條流水線,數據從源頭(FROM
)開始,經過一道道工序(WHERE
, GROUP BY
等)的加工和篩選,最終形成我們想要的產品(結果集)。理解了這個流程,你就能更清晰地思考問題,寫出邏輯正確、性能更優的 SQL 查詢。
SELECT 中的非聚合列必須出現在 GROUP BY 子句中,否則會報錯。
HAVING 子句用于過濾聚合函數的結果(如 SUM、COUNT、AVG 等)。
g.weight < 50 是對原始列的過濾,不屬于聚合條件,應該放在 WHERE 子句中。