SQL.11 查詢結果的列序:沒有 ORDER BY 時的來源與 ORDER BY 的規定範圍
一份 relation 是集合,而集合的成員沒有先後。所以一段查詢算出來的那份關係本身不帶順序,回來的那一批列排成什麼樣,是執行計畫走到最後剛好留下的形狀。ORDER BY 是把順序加回去的那個子句,也是唯一的一個——而它要寫在最外層才算數。寫在衍生表或視圖裡面的 ORDER BY 引擎可以丟掉:
1-- MySQL 8.4
2CREATE TABLE t (id INT PRIMARY KEY, k INT);
3INSERT INTO t VALUES (1, 30), (2, 10), (3, 20);
4
5SELECT group_concat(id) FROM (SELECT id FROM t ORDER BY k) d;
6-- 1,2,3 主鍵序;衍生表裡的 ORDER BY k 被丟掉了
7SELECT id FROM (SELECT id, k FROM t) d ORDER BY k;
8-- 2、3、1 ORDER BY 寫在最外層才照 k 排這個語言用一套規則決定哪些列會出現在結果裡(那套規則叫語意模型),而輸出的順序是它管得最少的一項:規則只到「排序鍵分得出高下」為止,分不出高下的那些它沒有規定。
本篇交代順序從哪裡來、ORDER BY 看得到什麼、以及比不出大小的值落在哪一端。ORDER BY 沒有規定同分的列誰先誰後,這件事在什麼時候變成錯誤,在 SQL.12 分頁的排序鍵與接續標記——整批看的時候同分的列換位置沒有後果,每一頁各查一次的時候它變成重複與遺漏;那一篇寫排序鍵要補到兩兩可分的判斷標準,以及把接續標記(cursor)從位置換成值的寫法。
本篇範圍之外的兩件事各有專篇。哪些列會進到這份結果由條件與連接決定,順序管不到那一層:條件擺哪一邊決定留下哪些列在 SQL.5 ON 與 WHERE:連接條件與篩選條件各自的職責,連接怎麼改變列數在 SQL.6 連接之後的列數與空缺。視窗函數的 OVER (ORDER BY ...) 是另一個順序,它決定計算時誰算相鄰、不決定輸出怎麼排,兩者可以不同,SQL.10 分組與視窗函數:各自的產出、選用的依據與 LAG、LEAD 的相鄰列 的〈排序相鄰與問題要的相鄰:分區、排序鍵與距離條件〉一節寫那一種。
本篇的查詢跑在共用資料庫的訂單表上,並把 101 的金額改成 700、再加三張,讓五張訂單裡有三張同樣是 500 元:
1UPDATE 訂單 SET 金額 = 700 WHERE 訂單編號 = 101;
2INSERT INTO 訂單 VALUES (103, 2, '2026-03-10', 500), (104, 2, '2026-03-11', 300),
3 (105, 3, '2026-03-12', 500);
4-- 101 是 700 元,102、103、105 是 500 元,104 是 300 元沒有 ORDER BY 時列序的來源
同一段查詢,同一批資料,中間只多建了一個索引:
1-- SQLite 3.51
2SELECT 訂單編號 FROM 訂單 WHERE 金額 >= 300;
3-- 101,102,103,104,105 計畫:SCAN 訂單
4
5CREATE INDEX ix ON 訂單(金額);
6
7SELECT 訂單編號 FROM 訂單 WHERE 金額 >= 300;
8-- 104,102,103,105,101 計畫:SEARCH 訂單 USING INDEX ix (金額>?)
9-- 索引照金額排:300 的 104、500 的三張(102、103、105)、700 的 101查詢的文字一個字都沒改,回來的順序整個換掉了。沒有索引的那一次照資料存放的先後走,建了索引的那一次照索引上的先後走——兩種都正確,因為那份關係本來就沒有規定過順序。
這件事在開發時多半看不見。一張小表、一種計畫,看到的順序穩定得像是有保證,而讓它變動的條件(建了索引、資料長大、統計更新、換一家引擎)沒有一項在查詢的文字裡。
ORDER BY 在求值順序裡的位置與它用得到的別名
ORDER BY 排的是 SELECT 已經算完的那份結果,所以 SELECT 裡取的別名它用得到:
1SELECT 金額 * 2 AS 兩倍 FROM 訂單 ORDER BY 兩倍 DESC;
2-- 三家都接受:1400、1000、1000、1000、600
3
4SELECT 金額 * 2 AS 兩倍 FROM 訂單 WHERE 兩倍 > 1000;
5-- PostgreSQL:column "兩倍" does not exist
6-- MySQL:ERROR 1054 Unknown column '兩倍' in 'where clause'
7-- SQLite:收下,回 1400WHERE 那一步發生在算出 兩倍 之前,所以那個名字在那裡還不存在。SQLite 收下同一段,這是它的寬鬆度而不是標準行為(求值順序的完整推導與這條寬鬆度的其他實例在 SQL.2 子句的求值順序:每一步可用的欄位、語意模型擋下的寫法與各家引擎放寬的寫法)。
NULL 在排序結果裡的位置與各家引擎的預設
排序鍵含空值的時候,NULL 與任何值都比不出大小,所以它落在哪一端由引擎自己規定。會同時是排序鍵又可能為空的欄位不少——選填的折扣金額、還沒完成的那些列的完成時間,都是拿來排序的常見對象。下面的表格與查詢把 103 的金額清成 NULL、刪掉 105,留下四列:
1UPDATE 訂單 SET 金額 = NULL WHERE 訂單編號 = 103;
2DELETE FROM 訂單 WHERE 訂單編號 = 105;
3-- 101 是 700、102 是 500、103 是 NULL、104 是 300| 引擎 | ORDER BY 金額 | ORDER BY 金額 DESC |
|---|---|---|
| SQLite 3.51 | NULL 排最前 | NULL 排最後 |
| MySQL 8.4 | NULL 排最前 | NULL 排最後 |
| PostgreSQL 18 | NULL 排最後 | NULL 排最前 |
| DuckDB v0.10.3 | NULL 排最後 | NULL 排最後 |
三家的規定各自自洽:SQLite 與 MySQL 把 NULL 當成比任何值都小,PostgreSQL 當成比任何值都大,DuckDB v0.10.3 則不論升冪降冪都把 NULL 排在最後——它的預設是 NULLS LAST,不隨 DESC 翻轉。要跨引擎一致就得寫出來,而寫法本身也分兩家:
1SELECT 訂單編號 FROM 訂單 ORDER BY 金額 NULLS LAST;
2-- PostgreSQL 18、SQLite 3.51、DuckDB v0.10.3:104,102,101,103
3-- MySQL 8.4:ERROR 1064 語法錯誤
4
5-- 先排一個布林值:金額是 NULL 的列 (金額 IS NULL) 為真,排在為假的列之後
6SELECT 訂單編號 FROM 訂單 ORDER BY (金額 IS NULL), 金額;
7-- PostgreSQL 18、SQLite 3.51、MySQL 8.4、DuckDB v0.10.3:104,102,101,103NULL 是比不出大小的那一種值;字串是比得出、而比法由另一條規則決定的那一種。排序鍵換成姓名時,大小寫算不算相同、重音字母排在哪裡,各家預設不同,同一批名字排出來的順序也不同——SQL.15 字串比較與 collation:相等、排序、LIKE 與索引可用性 用同一批名字在三家上排出三種順序。