集合演算
集合演算
🌍 実世界のアナロジー
2つの名刺の束を持っていると想像してください:
- UNION — 2つの束を結合し、重複は1つだけ保持
- UNION ALL — 両方の束のすべてのカードを結合し、重複も保持
- INTERSECT — 両方の束に出現するものだけを取り出す
- EXCEPT — 最初の束から、2番目の束にも出現するものを除去
集合演算は、2つのクエリ結果セットに対して「結合、積集合、差集合」の演算を実行します。
🎯 コアコンセプト
UNION — 重複除去付き結合
2つの結果セットを結合し、重複する行を自動的に除去します。
SELECT city FROM employees
UNION
SELECT city FROM departments;
UNION ALL — 重複除去なし結合
2つの結果セットを結合し、すべての重複行を保持します。重複除去が不要なため、パフォーマンスが向上 します。
SELECT city FROM employees
UNION ALL
SELECT city FROM departments;
INTERSECT — 積集合
両方の 結果セットに 存在する行 を返します。
SELECT city FROM employees
INTERSECT
SELECT city FROM departments;
EXCEPT / MINUS — 差集合
最初の結果セットに存在するが、2番目には 存在しない行 を返します。
-- EXCEPT(SQL Server、PostgreSQL)
SELECT city FROM employees
EXCEPT
SELECT city FROM departments;
-- MINUS(Oracle)
SELECT city FROM employees
MINUS
SELECT city FROM departments;
カラムマッチングルール
集合演算を使用する際、2つの SELECT 文は以下を満たす必要があります:
| ルール | 説明 |
|---|---|
| カラム数が同じ | 両方のSELECTは同じ数のカラムを持つ必要がある |
| 型が互換性がある | 対応するカラムは互換性のあるデータ型を持つ必要がある |
| ORDER BYは最後に | 文の最後に1回だけ使用可能 |
-- 正しい:2つのカラム、型が一致
SELECT first_name, salary FROM employees
UNION
SELECT department_name, budget FROM departments;
-- 間違い:カラム数が異なる
SELECT first_name, salary FROM employees
UNION
SELECT department_name; -- ❌ カラム数が不一致
💡 どの演算をいつ使用するか
| 演算 | シナリオ | 重複除去 | パフォーマンス |
|---|---|---|---|
| UNION | 結合して重複除去が必要 | ✅ あり | 遅い |
| UNION ALL | 重複除去なしで結合 | ❌ なし | 速い |
| INTERSECT | 共通部分を検索 | ✅ あり | 中程度 |
| EXCEPT | 差分を検索 | ✅ あり | 中程度 |
UNION ALL を優先してください。
📝 基本構文
-- UNION構文
SELECT カラム1, カラム2 FROM テーブル1
UNION
SELECT カラム1, カラム2 FROM テーブル2
[ORDER BY カラム1];
-- UNION ALL構文
SELECT カラム1, カラム2 FROM テーブル1
UNION ALL
SELECT カラム1, カラム2 FROM テーブル2;
-- INTERSECT構文
SELECT カラム1, カラム2 FROM テーブル1
INTERSECT
SELECT カラム1, カラム2 FROM テーブル2;
-- EXCEPT構文
SELECT カラム1, カラム2 FROM テーブル1
EXCEPT
SELECT カラム1, カラム2 FROM テーブル2;
- 集合演算では、カラム名は最初のSELECTによって決定されます
- ORDER BYは最後にのみ出現でき、通常はカラムの序数を使用します
- 各SELECTには独自のWHERE、GROUP BYを含めることができます
📌 例題
例:すべての名前のソースをクエリ
社員名と部門名を1つの結果セットに結合します。
-- すべての「名前」ソースを表示:社員 + 部門マネージャー
SELECT first_name AS name, '社員' AS source
FROM employees
UNION ALL
SELECT department_name, '部門'
FROM departments
ORDER BY source;
説明:データソースを区別するために、定数列を追加しています。
例:社員がいるが部門がない都市を検索
-- 社員がいる都市から部門がある都市を引く
SELECT city FROM employees
EXCEPT
SELECT city FROM departments;
アプローチ:EXCEPT を差集合演算として使用し、「社員テーブルにのみ出現する都市」を迅速に検索します。
🎬 シナリオ詳細
シナリオ1:マルチチャネル顧客リストの結合
オンラインとオフラインの顧客テーブルを結合・重複除去し、完全な顧客ディレクトリを作成します。
-- オンライン顧客
SELECT customer_name, email, 'オンライン' AS channel
FROM online_customers
UNION
-- オフライン顧客
SELECT customer_name, email, 'オフライン'
FROM offline_customers
ORDER BY customer_name;
ポイント:重複する顧客を自動的に除去するために、UNION ALL ではなく UNION を使用します。
シナリオ2:2か月間の売上差異を比較
今月は売上があったが先月はなかった商品を検索します。
-- 今月売れた商品
SELECT product_id FROM orders
WHERE order_date >= '2026-06-01'
EXCEPT
-- 先月売れた商品
SELECT product_id FROM orders
WHERE order_date >= '2026-05-01'
AND order_date < '2026-06-01';
ポイント:EXCEPT は「差分を検索」シナリオに天然適しています。
❓ よくある質問
質問:UNIONとUNION ALLのどちらをいつ使用すべきですか? 回答: 重複除去が不要な場合は、余分な重複除去ステップが不要でパフォーマンスが良いため、
UNION ALLを優先してください。重複除去が特に必要な場合のみUNIONを使用してください。
質問:集合演算でカラム名が一致しない場合はエラーになりますか? 回答: いいえ、結果セットのカラム名は最初の
SELECTによって決定されます。ただし、カラム数と対応するカラムのデータ型は一致する必要があります。
質問:INTERSECTとJOINの違いは何ですか? 回答:
INTERSECTは行全体を照合して積集合を求めますが、JOINは指定された条件に基づいてテーブルを関連付けます。「同じ行を検索」する必要がある場合はINTERSECTを使用し、「異なるテーブルのカラムをフィールドで関連付ける」必要がある場合はJOINを使用してください。
質問:MySQLはINTERSECTとEXCEPTをサポートしていますか? 回答: MySQL 8.0以降は
INTERSECTとEXCEPTをサポートしています。以前のバージョンでは、INNER JOINやNOT EXISTS/LEFT JOIN ... IS NULLを使用してシミュレートする必要があります。
📖 まとめ
| 演算 | 目的 | 重複除去 |
|---|---|---|
| UNION | 2つの結果セットを結合 | ✅ |
| UNION ALL | 2つの結果セットを結合(重複を保持) | ❌ |
| INTERSECT | 2つの結果セットの積集合 | ✅ |
| EXCEPT | 2つの結果セットの差集合 | ✅ |
- カラム数と型は一致する必要があります
ORDER BYは文の最後にのみ配置できます- 重複がないことが確実な場合は、パフォーマンス向上のため
UNION ALLを優先してください
📝 演習
UNIONを使用して、employeesテーブルから給与が8000を超える社員と、departmentsテーブルから予算が100000を超える部門名を結合してください。INTERSECTを使用して、employeesテーブルとdepartmentsテーブルの両方に存在する都市を検索してください。EXCEPTを使用して、部門があるが社員が割り当てられていない都市を検索してください。- 考えてみましょう:集合演算で2つのクエリ結果のカラム型が完全に一致しない場合(例:INTとVARCHAR)、どうなりますか?
次のレッスン
次は 制約とキー を学びます。PRIMARY KEY、FOREIGN KEY、その他の制約を使用してデータ整合性を確保する方法を理解します。



