【SQL】UNIONとORDER BYの完全ガイド|ソート位置の制限・ソース別ソート・ピン留め・各UNION部分への個別ORDER BY・DBMS別の違いまで解説

【SQL】UNIONとORDER BYを同時に使用する方法 SQL

UNIONで複数SELECTの結果を結合したあとにORDER BYで並び替えるのは一見シンプルです。しかし「各SELECT内にORDER BYを書いたらエラーになった」「ORDER BYで参照できる列名が制限される」「特定のテーブルのデータを先に表示したい」といった疑問にぶつかるケースが多くあります。

本記事ではUNIONとORDER BYの組み合わせに関するルール・よくあるエラーの原因と対策・ソース別の優先ソートやピン留めといった実務パターン・各UNION部分にORDER BYをかけたい場合の回避策・DBMS別の挙動差まで徹底解説します。

この記事で学べること:UNION + ORDER BYの基本ルール・ORDER BYを書ける位置の制約・参照できる列名の制約(最初のSELECTの列名のみ)・各UNION部分に個別ORDER BY/LIMITをかける方法(サブクエリ・CTE)・ソース識別子を使ったソース別優先ソート・特定行のピン留め・UNION ALL vs UNIONのパフォーマンス・DBMS別の注意点
スポンサーリンク

基本ルール:ORDER BYはUNION全体の末尾に1つだけ

UNIONを使うとき、ORDER BYに関して守るべきルールが2つあります。

  • ルール1:ORDER BYはUNION全体の末尾に1つだけ書く
  • ルール2:ORDER BYで参照できる列名は最初のSELECT文の列名(エイリアス含む)に限られる
基本の書き方
-- OK: ORDER BY は UNION 全体の末尾に1つ
SELECT id, name, 'users'  AS source FROM users
UNION
SELECT id, name, 'admins' AS source FROM admins
ORDER BY name ASC;   -- ← ここに1つだけ書く

-- OK: UNION ALL(重複を残す)+ ORDER BY
SELECT id, name FROM employees
UNION ALL
SELECT id, name FROM contractors
ORDER BY id;
各SELECT内にORDER BYは書けません(エラーになります):
UNION結合する各SELECTの末尾にORDER BYを書くと、MySQL・PostgreSQL・SQL Serverではエラーになります。
個別SELECTにORDER BYをかけたい場合はサブクエリまたはCTEを使います(後述)。

ORDER BYで参照できる列名の制約

ORDER BYで使える列名は最初のSELECT文の列名・エイリアスに限られます。2番目以降のSELECTの列名が違う場合でも、最初のSELECTの名前で参照します。

列名の制約と列番号での参照
-- テーブルによって列名が異なる場合
-- employees テーブル: emp_name
-- contractors テーブル: contractor_name

-- OK: 最初のSELECTの列名(または列番号)を使う
SELECT emp_id AS id, emp_name AS name FROM employees
UNION
SELECT contractor_id, contractor_name FROM contractors
ORDER BY name;   -- 最初のSELECTのエイリアス "name" を参照

-- OK: 列番号でも参照できる(可読性は下がる)
SELECT emp_id, emp_name FROM employees
UNION
SELECT contractor_id, contractor_name FROM contractors
ORDER BY 2;      -- 2番目の列(emp_name / contractor_name)でソート

-- NG: 2番目のSELECTの列名は使えない
SELECT emp_id, emp_name FROM employees
UNION
SELECT contractor_id, contractor_name FROM contractors
ORDER BY contractor_name;   -- エラー! contractor_name は最初のSELECTにない
列番号参照は非推奨:列番号(ORDER BY 2)はSQLが読みにくくなり、SELECTの列順を変更したときにバグの原因になります。可読性のためにエイリアスを使った列名参照が推奨です。

各UNION部分に個別のORDER BYまたはLIMITをかけたい場合

「各テーブルの上位N件ずつをUNIONして表示したい」など、個別SELECTにORDER BYやLIMITをかけたい場合は、サブクエリ(または括弧付きSELECT)を使います。

各テーブルから上位N件をUNIONする(MySQL / PostgreSQL)
-- MySQL / PostgreSQL: 括弧でくくることで個別LIMITが使える
(SELECT id, name, score FROM game_rankings ORDER BY score DESC LIMIT 3)
UNION ALL
(SELECT id, name, score FROM mobile_rankings ORDER BY score DESC LIMIT 3)
ORDER BY score DESC;

-- ※ UNION(重複排除)を使う場合も同様
(SELECT emp_id AS id, emp_name AS name FROM employees ORDER BY emp_name LIMIT 5)
UNION
(SELECT contractor_id, contractor_name FROM contractors ORDER BY contractor_name LIMIT 5)
ORDER BY name;
各テーブルから上位N件をUNIONする(Oracle)
-- Oracle: 括弧内のORDER BYは許可されていない(エラー)
-- → サブクエリ + ROWNUM / FETCH FIRST を使う
SELECT id, name, score
FROM (
    SELECT id, name, score FROM game_rankings ORDER BY score DESC FETCH FIRST 3 ROWS ONLY
)
UNION ALL
SELECT id, name, score
FROM (
    SELECT id, name, score FROM mobile_rankings ORDER BY score DESC FETCH FIRST 3 ROWS ONLY
)
ORDER BY score DESC;
各テーブルから上位N件をUNIONする(SQL Server)
-- SQL Server: サブクエリ + TOP を使う
SELECT id, name, score
FROM (SELECT TOP 3 id, name, score FROM game_rankings    ORDER BY score DESC) AS g
UNION ALL
SELECT id, name, score
FROM (SELECT TOP 3 id, name, score FROM mobile_rankings  ORDER BY score DESC) AS m
ORDER BY score DESC;
CTEを使うとより読みやすい(全DB共通)
-- CTEを使った書き方(推奨:可読性が高い)
WITH top_employees AS (
    SELECT emp_id AS id, emp_name AS name, 'employee' AS source
    FROM   employees
    ORDER BY emp_name
    LIMIT 5   -- MySQL / PostgreSQL
    -- FETCH FIRST 5 ROWS ONLY  -- Oracle
    -- TOP 5 を先頭に書く       -- SQL Server
),
top_contractors AS (
    SELECT contractor_id, contractor_name, 'contractor' AS source
    FROM   contractors
    ORDER BY contractor_name
    LIMIT 5
)
SELECT id, name, source FROM top_employees
UNION ALL
SELECT id, name, source FROM top_contractors
ORDER BY name;

ソース別の優先ソート(テーブルAを先に、テーブルBを後に)

「社員を先に表示して、次に業務委託を表示したい」など、特定のSELECTのデータを別のSELECTより優先して並べたい場合は、ソート用の識別子カラムをSELECT句に追加します。

ソース識別子でテーブルの優先順位を制御
-- source_order 列で優先順位を指定(1が先、2が後)
SELECT emp_id AS id, emp_name AS name,
       1 AS source_order    -- 社員を先
FROM   employees
UNION ALL
SELECT contractor_id, contractor_name,
       2 AS source_order    -- 業務委託を後
FROM   contractors
ORDER BY source_order, name;  -- まずテーブル順、次に名前順

-- 結果のイメージ:
-- id | name    | source_order
--  1 | 田中   |     1(社員)
--  2 | 鈴木   |     1(社員)
--  3 | 佐藤   |     2(業務委託)
--  4 | 渡辺   |     2(業務委託)
ソース識別子をSELECT結果から非表示にする方法
-- ソート用の列を表示させたくない場合: サブクエリでラップして除外
SELECT id, name
FROM (
    SELECT emp_id   AS id, emp_name   AS name, 1 AS sort_key FROM employees
    UNION ALL
    SELECT contractor_id, contractor_name, 2 AS sort_key FROM contractors
) AS combined
ORDER BY sort_key, name;  -- sort_key でソートしつつ、外側のSELECTには含めない

ピン留め:特定のレコードを常に先頭に固定する

「「おすすめ」ラベルのついた商品を常に先頭に表示し、残りは通常ソート」といったUI要件は、UNIONとソート用カラムの組み合わせで実現できます。

おすすめ商品を先頭に固定表示
-- おすすめ商品(is_featured = 1)を先頭に、残りを名前順で表示
SELECT product_id, name, price, 1 AS pin_order FROM products WHERE is_featured = 1
UNION ALL
SELECT product_id, name, price, 2 AS pin_order FROM products WHERE is_featured = 0
ORDER BY pin_order, name;

-- 同じ結果をCASE WHENで1クエリにする方法(UNIONなし)
SELECT product_id, name, price
FROM   products
ORDER BY
    CASE WHEN is_featured = 1 THEN 0 ELSE 1 END,  -- おすすめを先頭
    name;   -- 同じグループ内は名前順
ピン留めはUNION不要な場合も多い:同じテーブルから取得する場合は、CASE WHEN を ORDER BY に使う方がシンプルです。UNIONが有効なのは異なるテーブルや異なるクエリ条件を組み合わせる場合です。
管理者レコードを先頭、一般ユーザーを後方に固定
-- 管理者(admins)→ 一般ユーザー(users)の順に表示
SELECT user_id AS id, username AS name, email, 'admin' AS role, 1 AS sort_key
FROM   admins
UNION ALL
SELECT user_id, username, email, 'user' AS role, 2 AS sort_key
FROM   users
ORDER BY sort_key, name;

各UNION部分の行の順序を保持する

「テーブルAはスコア降順、テーブルBは日付順に並べてからUNION」のように、各部分の内部順序を保ったまま結合したい場合は、行番号列(ROW_NUMBER)を使います。

各UNION部分の内部順序を保持(ROW_NUMBER使用)
-- ROW_NUMBER() を使って各部分の内部順序を保持
SELECT id, name, score, source, part_rank
FROM (
    SELECT
        game_id     AS id,
        game_name   AS name,
        high_score  AS score,
        'game'      AS source,
        ROW_NUMBER() OVER (ORDER BY high_score DESC) AS part_rank,
        1           AS source_order
    FROM game_scores
    UNION ALL
    SELECT
        user_id,
        username,
        level_score,
        'user',
        ROW_NUMBER() OVER (ORDER BY level_score DESC),
        2
    FROM user_scores
) ranked
ORDER BY source_order, part_rank;
-- game_scores: スコア降順の順序を保持
-- user_scores: スコア降順の順序を保持
-- 2つのブロックを順番に表示

UNION vs UNION ALL + ORDER BYのパフォーマンス

ORDER BYを加えるとソートコストが発生しますが、それに加えてUNION(重複排除)はUNION ALLよりも重いです。理由は重複排除のためにソートまたはハッシュ処理が必要なためです。

組み合わせ 動作 コスト 推奨場面
UNION ALL + ORDER BY 全行マージ → ソート 低(ソートのみ) 重複がない・または重複を残したい場合
UNION + ORDER BY 全行マージ → 重複排除 → ソート 高(重複排除+ソート) 重複排除が必要な場合のみ
UNION ALL を優先して使う:重複がないことが確実な場合(異なるテーブルを結合するケースの多く)は、UNION ALLの方が高速です。UNIONは重複排除のための処理が走るため、件数が多いほどコスト差が大きくなります。
EXPLAINで実行計画を確認(MySQL)
-- UNION + ORDER BY の実行計画を確認
EXPLAIN
SELECT id, name FROM employees
UNION
SELECT id, name FROM contractors
ORDER BY name;

-- UNION ALL + ORDER BY と比較
EXPLAIN
SELECT id, name FROM employees
UNION ALL
SELECT id, name FROM contractors
ORDER BY name;
-- UNION ALLの方がExtra列に "Using filesort" のみ(重複排除コストなし)

DBMS別の注意点

MySQL
-- MySQL: 括弧付きSELECTに個別のORDER BY + LIMIT が使える
(SELECT id, name FROM users    ORDER BY name LIMIT 5)
UNION ALL
(SELECT id, name FROM admins   ORDER BY name LIMIT 5)
ORDER BY name;

-- ただし括弧なしのSELECTにORDER BYを書くとエラー
-- SELECT id, name FROM users ORDER BY name  ← エラー
-- UNION ALL
-- SELECT id, name FROM admins
-- ORDER BY name;

-- MySQL: ORDER BY にエイリアスが使える(拡張)
SELECT id, emp_name AS name FROM employees
UNION ALL
SELECT id, contractor_name FROM contractors
ORDER BY name;  -- エイリアス "name" が使える
PostgreSQL
-- PostgreSQL: 括弧付きSELECT内のORDER BYは許可される
(SELECT id, name FROM users   ORDER BY name LIMIT 5)
UNION ALL
(SELECT id, name FROM admins  ORDER BY name LIMIT 5)
ORDER BY name;

-- PostgreSQL: SQL標準に厳密で、ORDER BYの列名は最初のSELECTを参照
SELECT emp_id AS id, emp_name AS name FROM employees
UNION
SELECT contractor_id, contractor_name FROM contractors
ORDER BY name;  -- 最初のSELECTのエイリアス "name" を使う
Oracle
-- Oracle: UNION内の各SELECTにORDER BYは書けない
-- → サブクエリ + ROWNUM / FETCH FIRST を使う

-- OK: サブクエリでORDER BY + FETCH FIRST
SELECT id, name FROM (
    SELECT emp_id AS id, emp_name AS name FROM employees
    ORDER BY emp_name FETCH FIRST 5 ROWS ONLY
)
UNION ALL
SELECT id, name FROM (
    SELECT contractor_id, contractor_name FROM contractors
    ORDER BY contractor_name FETCH FIRST 5 ROWS ONLY
)
ORDER BY name;

-- Oracle: UNION全体のORDER BYでエイリアスが使える
SELECT emp_id AS id, emp_name AS name FROM employees
UNION
SELECT contractor_id, contractor_name FROM contractors
ORDER BY name;  -- Oracle もエイリアス参照可
SQL Server
-- SQL Server: 括弧付きSELECT内のORDER BYは使えない(エラー)
-- → サブクエリ + TOP を使う
SELECT id, name FROM (
    SELECT TOP 5 emp_id AS id, emp_name AS name
    FROM employees ORDER BY emp_name
) AS e
UNION ALL
SELECT id, name FROM (
    SELECT TOP 5 contractor_id, contractor_name
    FROM contractors ORDER BY contractor_name
) AS c
ORDER BY name;

-- SQL Server: ORDER BY のエイリアスも使える
SELECT emp_id AS id, emp_name AS name FROM employees
UNION
SELECT contractor_id, contractor_name FROM contractors
ORDER BY name;
機能 MySQL PostgreSQL Oracle SQL Server
括弧内の個別ORDER BY × (サブクエリ必要) × (サブクエリ必要)
括弧内の個別LIMIT ○ (LIMIT) ○ (FETCH FIRST) ○ (TOP)
ORDER BY でエイリアス参照
ORDER BY で列番号参照

よくあるエラーと原因・対処法

エラー内容 原因 対処法
ORDER BY is not allowed here UNION内のSELECTにORDER BYを書いた ORDER BYはUNION末尾に1つだけ書く。個別にかけたいならサブクエリを使う
Column not found in ORDER BY 2番目以降のSELECTの列名をORDER BYで参照した 最初のSELECTの列名またはエイリアスを使う
Incorrect usage of UNION and ORDER BY MySQLで括弧なしSELECTにORDER BYを書いた 括弧でSELECTをくくる: (SELECT ... ORDER BY ...)
SELECT列数が一致しない UNIONする各SELECTの列数が違う SELECT句の列数をすべてのSELECTで一致させる

よくある質問

Q各SELECTに個別のORDER BYをかけることはできますか?
A直接書くとエラーになりますが、MySQL・PostgreSQLでは各SELECTを括弧でくくることで個別のORDER BY + LIMITが使えます。Oracle・SQL Serverではサブクエリ(FETCH FIRST / TOP)を使います。CTEを使って事前にソート済みの結果を用意してUNIONする方法が最も読みやすくおすすめです。
QORDER BYで2番目のSELECTの列名を参照するとエラーになります
AUNION後のORDER BYで使える列名は最初のSELECTの列名(またはエイリアス)に限られます。2番目以降のSELECTの列名は使えません。解決策: ①最初のSELECTにエイリアスを付けて統一する、②列番号(ORDER BY 2)を使う(非推奨)、③サブクエリでラップして列名を統一する。
QUNIONとUNION ALLどちらを使うべきですか?
A異なるテーブルから異なるデータを取ってくる場合(重複が発生しない場合)はUNION ALLを使ってください。UNION ALLの方が重複排除の処理がない分高速です。重複排除が本当に必要な場合のみUNIONを使います。
QテーブルAのデータを先に、テーブルBのデータを後に表示するには?
A各SELECTにソート用の数値カラム(例: 1 AS sort_key2 AS sort_key)を追加して、ORDER BY sort_key, name と並べます。sort_keyを表示させたくない場合はサブクエリでラップして外側のSELECTからsort_keyを除きます。
QUNIONを使わずに同じ結果を出す方法はありますか?
A同一テーブルから条件を変えてUNIONしている場合は、WHERE condition1 OR condition2WHERE condition IN (...)で1クエリにまとめられる場合があります。異なるテーブルをUNIONしている場合は代替が難しいですが、FULL OUTER JOIN(JOIN後にフィルタ)での代替も検討できます。

まとめ

UNIONとORDER BYの組み合わせには独自のルールがあります。ポイントをまとめます。

  • ORDER BYの位置:UNION全体の末尾に1つだけ。各SELECTには書けない
  • 列名の制約:ORDER BYで参照できるのは最初のSELECTの列名(エイリアス)のみ
  • 個別ORDER BY:MySQL/PostgreSQLは括弧でくくれば可。Oracle/SQL Serverはサブクエリ使用
  • ソース別優先ソート:ソート用数値カラム(sort_key)をSELECTに追加してORDER BYで利用
  • ピン留め:CASE WHEN(同一テーブル)またはUNION + sort_keyで実現
  • パフォーマンス:重複がなければUNION ALLを使う。UNIONは重複排除コストが発生
迷ったときの判断基準:各SELECTに個別のORDER BY/LIMITをかけたい → 括弧またはCTE、テーブルの優先順位を制御したい → sort_key列を追加、重複が生じない → UNION ALLを使って高速化

関連記事:UNION完全ガイド(UNION ALL・INTERSECT・EXCEPT/MINUSの違いと実務パターン)ORDER BYの使い方完全解説WITH句(CTE)を使って副問合せを再利用する方法