集合演算 UNION/INTERSECT/EXCEPT

集合演算 UNION/INTERSECT/EXCEPT とは

UNION、INTERSECT、EXCEPTを使ったSQL集合演算を実践的に学習します。本レッスンでは、集合演算 UNION/INTERSECT/EXCEPT の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

なぜ重要か

結合 (JOIN) は「横につなぐ」操作ですが、集合演算 は「縦に重ねる」操作です。「店舗 A の顧客と店舗 B の顧客を 1 つのリストにまとめる」「両店舗共通の顧客だけを出す」「片方にしかいない顧客を出す」など、リスト同士を合成・比較するときに使います。レポート作成や差分抽出で頻出するので、JOIN とセットで必ず覚えておきましょう。集合演算は数学の集合論にそのまま対応しており、ベン図で考えると挙動が分かりやすくなります。

3 つの演算子

演算子意味
UNION2 つの結果を縦に合成 (重複は除去)
UNION ALL縦に合成 (重複もそのまま残す)
INTERSECT両方に共通する行だけ
EXCEPT左にあるが右にない行 (MySQL 8 以降 / PostgreSQL)

MySQL では INTERSECTEXCEPT はどちらも 8.0.31 で同時に追加されました。それより前のバージョンでは使えません。PostgreSQL は古くからすべて対応しています。

構文

2 つの SELECT 文を演算子でつなぎます。列数と列の型が一致している必要があります。

SQL クエリ

SELECT 列リスト FROM テーブル A UNION SELECT 列リスト FROM テーブル B;

3 つ以上の SELECT を縦に合成することもでき、その場合は演算子を縦に並べていきます。

ステップごとの動作

下の図は 2 つの顧客リストを集合演算で合成するイメージです。

diagram (will load when visible)

代表例 1 — 2 店舗の顧客を統合

SQL クエリ

SELECT name FROM tokyo_customers UNION SELECT name FROM osaka_customers ORDER BY name;

両店舗を合わせた顧客名簿を、重複を取り除いて並べます。CRM ツールへのインポート用リストや、合計顧客数の算出に使います。

代表例 2 — 共通顧客と片方だけの顧客

SQL クエリ

-- 両店舗に存在する顧客 SELECT name FROM tokyo_customers INTERSECT SELECT name FROM osaka_customers; -- 東京にいるが大阪にいない顧客 SELECT name FROM tokyo_customers EXCEPT SELECT name FROM osaka_customers;

顧客重複や脱落を発見するのに重宝します。データ移行前の整合性チェックや、A/B テストの対象比較でもよく使います。

UNION と UNION ALL の違い

UNION は重複を消すために内部でソートが走り、UNION ALL より遅くなりがちです。重複が無いと分かっているなら UNION ALL を使ったほうがパフォーマンスは良いです。逆に、重複を確実に消したい場合は UNION を選びましょう。同じデータが 100 万行 × 2 で並ぶような統計ジョブでは、UNION ALL を選ぶだけで体感速度が大きく変わることもあります。

列の型・順序ルール

集合演算では「すべての SELECT が同じ列数 + 同じ列の型」を持つ必要があります。型が違うと暗黙の型変換が走り、エラーになるか想定外の結果になります。列名は最初の SELECT のものが採用されるので、AS で揃えておくと安心です。レビューでは「列の順序と型が揃っているか」を最初に見ると、バグを発見しやすくなります。

ORDER BY の位置

集合演算した結果に対して並び替えたい場合は、最後の SELECT の後にまとめて 1 つだけ ORDER BY を書きます。個別の SELECT に ORDER BY を書いてもパース時にエラーになるか、無視されることがあります。

集合演算と JOIN の使い分け

  • 横に増やしたい (列を追加) → JOIN
  • 縦に増やしたい (行を追加) → UNION / UNION ALL
  • 共通行だけ欲しい → INTERSECT (または JOIN + DISTINCT)
  • 差分だけ欲しい → EXCEPT (または LEFT JOIN + IS NULL)

後者 2 つは JOIN で書ける場合もありますが、集合演算で書いたほうが意図が明確になることが多いです。コードレビューで意図が伝わる SQL を書けるかが、中級者と上級者の分かれ目になります。

集合演算でよくあるユースケース

  • 複数店舗のログを 1 つにまとめる
  • 旧テーブルと新テーブルの差分を確認する
  • A/B テストの両グループに重複参加した人を特定する
  • システム移行前後のデータを比較する

どれも「縦にデータを足し算 / 引き算する」操作です。横方向に列を増やす JOIN とは違う発想なので、両方を使い分けられるようになると SQL の引き出しが一気に増えます。

まとめ

  • UNION は縦合成 + 重複除去、UNION ALL は重複も残す
  • INTERSECT は共通行、EXCEPT は差分
  • 列数と列の型は完全に一致させる
  • 並び順は最後にまとめて ORDER BY で指定する
  • パフォーマンス重視なら可能な限り UNION ALL を選ぶ
  • 「縦に増やすか横に増やすか」で JOIN と使い分ける

次のレッスン

次は 結合の実践演習 です。複数のテーブルを結合する実践的な演習です。JOIN句を使いこなしましょう。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 集合演算 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 集合演算 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

テーブル構造

schema.sql

CREATE TABLE tokyo_customers ( id INT, name VARCHAR(30) ); CREATE TABLE osaka_customers ( id INT, name VARCHAR(30) ); INSERT INTO tokyo_customers VALUES (1, '田中'), (2, '鈴木'), (3, '佐藤'), (4, '高橋'); INSERT INTO osaka_customers VALUES (5, '佐藤'), (6, '鈴木'), (7, '山田'), (8, '伊藤');

期待される出力

customer_name
伊藤
佐藤
山田
田中
鈴木
高橋

ヒント

query.sql
query.sql
学習モード
コードの実行結果
データベースを初期化中...