UNION 是合併多個 SELECT 結果集的利器,但其排序規則、去重機制與分頁用法常讓開發者踩坑。本文系統梳理 UNION 核心語法、排序原理、ThinkPHP 等框架下的複雜實現,並給出論壇「置頂+最新」混合分頁的最佳實踐。
SELECT ...
UNION [ALL | DISTINCT]
SELECT ...
[UNION [ALL | DISTINCT] SELECT ...]
ALL 時等同 UNION DISTINCT,會對全量結果排序去重,性能開銷大。SELECT 列數、順序、類型必須一致;結果集列名以第一個 SELECT 為準。SELECT 可用 INTO OUTFILE;HIGH_PRIORITY 僅對第一個生效,其餘報錯。(SELECT a FROM tbl WHERE a=10 AND b=1)
UNION
(SELECT a FROM tbl WHERE a=11 AND b=2)
ORDER BY a LIMIT 10;
⚠️ 關鍵規則:ORDER BY 不能使用 表名.列名,必須用第一個 SELECT 的列別名或列位置(推薦別名)。
-- 正確:使用別名
(SELECT a AS b FROM t) UNION (SELECT ...) ORDER BY b;
-- 錯誤:使用原列名
(SELECT a AS b FROM t) UNION (SELECT ...) ORDER BY a; -- Unknown column 'a'
(SELECT a FROM tbl WHERE a=10 ORDER BY a LIMIT 10)
UNION
(SELECT a FROM tbl WHERE a=11 ORDER BY a LIMIT 10);
此時各子查詢先各自排序截取,再合併(不再全局排序)。
需求:置頂帖(top=1)按時間倒序,普通帖(top=0)按時間倒序,合併後統一分頁。
(SELECT * FROM `blog` WHERE top=1 ORDER BY created DESC)
UNION
(SELECT * FROM `blog` WHERE top=0 ORDER BY created DESC)
LIMIT 2, 3; -- 跳過前 2 條,取 3 條
注意:兩端 SELECT * 列數須一致;若結構不同,用 NULL 佔位對齊。
| 特性 | UNION | UNION ALL |
|---|---|---|
| 去重 | 是(全量排序去重) | 否(直接拼接) |
| 速度 | 慢(臨時表+文件排序) | 快(流式輸出) |
| 適用場景 | 結果集可能重複,且需去重 | 確定無重複,或業務允許重複 |
典型案例:當前表 UNION 歷史表 確無重複主鍵 → 必用 UNION ALL。
單條 SQL 實現:
SELECT *, 1 AS ord FROM `women`
WHERE status=2 AND (title LIKE '%2014%' OR title LIKE '%秋冬%')
UNION
(SELECT *, 2 AS ord FROM `women`
WHERE status=2 AND (keywords LIKE '%2014%' OR keywords LIKE '%秋冬%'))
ORDER BY ord, id DESC LIMIT 0, 15;
原理:用常量列 ord 標記優先級,外層按 ord, id DESC 排序,實現分組內各自排序 + 分組間優先級排序。
框架 union() 方法通常僅支持簡單合併。複雜場景(如子查詢排序、聯合後再排序)建議:
query() 或 execute() 直接寫原生 SQL。UNION,確無重複用 UNION ALL(快 3-5 倍)。ORDER BY 別名。ORDER BY LIMIT。NULL AS col 對齊。