Contents

SQL union 排序的問題

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;

參考資料