SQL 的 UNION 在使用排序和處理 NULL 值時有些容易踩雷的細節,這篇整理正確的寫法和注意事項。
UNION 和 UNION ALL 的差異
| 特性 |
UNION |
UNION ALL |
| 重複資料 |
自動去除重複列 |
保留所有列(含重複) |
| 效能 |
較慢(需要排序去重) |
較快 |
| 適用場景 |
需要唯一結果時 |
確定無重複或不在意重複時 |
1
2
3
4
5
6
7
8
9
|
-- 去除重複
SELECT id FROM table_a
UNION
SELECT id FROM table_b;
-- 保留重複,效能較好
SELECT id FROM table_a
UNION ALL
SELECT id FROM table_b;
|
UNION 排序的正確寫法
ORDER BY 只能放在整個 UNION 查詢的最後,對整個結果集排序:
1
2
3
4
5
6
7
8
9
10
|
-- 正確:ORDER BY 放在最後
SELECT id, name FROM table_a
UNION
SELECT id, name FROM table_b
ORDER BY name;
-- 錯誤:不能在中間的 SELECT 加 ORDER BY
SELECT id, name FROM table_a ORDER BY name -- 這在多數 DB 會報錯
UNION
SELECT id, name FROM table_b;
|
子查詢中的排序
如果確實需要對個別子查詢排序後再 UNION,可以用子查詢包裝:
1
2
3
4
5
6
7
8
|
SELECT * FROM (
SELECT id, name, 1 AS sort_order FROM table_a ORDER BY name LIMIT 100
) sub_a
UNION ALL
SELECT * FROM (
SELECT id, name, 2 AS sort_order FROM table_b ORDER BY name LIMIT 100
) sub_b
ORDER BY sort_order, name;
|
⚠️ 注意:在子查詢中使用 ORDER BY 時,若沒有 LIMIT,部分資料庫(如 MySQL)可能會忽略子查詢的排序。
UNION 時 NULL 值的行為
當 UNION 的不同查詢欄位型態不同,或某查詢使用 NULL 佔位時,所有欄位會被強制轉型成統一的資料型態:
1
2
3
|
SELECT seq FROM ooo
UNION
SELECT NULL AS seq FROM xxx;
|
如果 ooo.seq 是數值型態(如 INTEGER),加入 NULL 後整個欄位仍保持數值型態,NULL 值本身不受影響。
但是反過來可能有問題:
1
2
3
4
|
-- 如果這樣寫,第一個 SELECT 的 NULL 會導致欄位型態變成通用型態
SELECT NULL AS seq FROM xxx
UNION
SELECT seq FROM ooo;
|
在某些資料庫中,若第一個 SELECT 的欄位是 NULL,整個 UNION 的欄位型態可能被推斷為字串,導致後續的數值比較出錯。建議明確轉型:
1
2
3
|
SELECT CAST(NULL AS INTEGER) AS seq FROM xxx
UNION
SELECT seq FROM ooo;
|
UNION 搭配 ORDER BY 的完整範例
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-- 合併兩個查詢並排序
SELECT
order_id,
customer_name,
'online' AS channel
FROM online_orders
UNION ALL
SELECT
order_id,
customer_name,
'offline' AS channel
FROM offline_orders
ORDER BY customer_name ASC, order_id DESC;
|
使用 ORDER BY 欄位名稱或位置
1
2
3
4
5
6
7
8
9
10
11
|
-- 用欄位名稱排序(建議)
SELECT id, name FROM table_a
UNION
SELECT id, name FROM table_b
ORDER BY name;
-- 用欄位位置排序(以第 2 個欄位排序)
SELECT id, name FROM table_a
UNION
SELECT id, name FROM table_b
ORDER BY 2;
|
參考資料