Oracle の MINUS 演算子は、1 つ目の SELECT の結果から 2 つ目の SELECT の結果を除外して差分を取得します。「テーブル A にはあるがテーブル B にはないデータ」を抽出する最もシンプルな方法です。
しかし「MINUS と NOT EXISTS はどう使い分けるのか」「NULL を含む行はどう扱われるのか」「大量データでのパフォーマンスはどうか」といった疑問もあります。
本記事では、MINUS の基本から、NOT EXISTS / NOT IN / LEFT JOIN との比較、NULL の扱い、Oracle 21c の EXCEPT / MINUS ALL、そしてテーブル差分検出・マスタ不整合チェックの実務パターンまで体系的に解説します。
この記事でわかること
・MINUS 演算子の基本構文と動作(自動 DISTINCT)
・MINUS と NOT EXISTS / NOT IN / LEFT JOIN の違いとパフォーマンス比較
・MINUS での NULL の扱い(NULL 同士は等しいとみなされる)
・Oracle 21c の EXCEPT / MINUS ALL
・集合演算 3 種(UNION / INTERSECT / MINUS)の比較
・ORDER BY / 列数・データ型の注意点
・テーブル差分検出・マスタ不整合チェック・データ移行検証の実務パターン
・MINUS 演算子の基本構文と動作(自動 DISTINCT)
・MINUS と NOT EXISTS / NOT IN / LEFT JOIN の違いとパフォーマンス比較
・MINUS での NULL の扱い(NULL 同士は等しいとみなされる)
・Oracle 21c の EXCEPT / MINUS ALL
・集合演算 3 種(UNION / INTERSECT / MINUS)の比較
・ORDER BY / 列数・データ型の注意点
・テーブル差分検出・マスタ不整合チェック・データ移行検証の実務パターン
MINUS の基本構文と動作
SQL(MINUS 基本)
-- 構文 SELECT column1, column2 FROM table_a MINUS SELECT column1, column2 FROM table_b; -- table_a にあって table_b にない行を返す
SQL(具体例)
-- テーブル A: employees_2024 -- emp_id: 1(Alice), 2(Bob), 3(Carol), 4(Dave) -- テーブル B: employees_2025 -- emp_id: 1(Alice), 2(Bob), 5(Eve) -- A MINUS B: A にあって B にない = 退職者 SELECT emp_id, emp_name FROM employees_2024 MINUS SELECT emp_id, emp_name FROM employees_2025; -- 結果: 3(Carol), 4(Dave) -- B MINUS A: B にあって A にない = 新入社員 SELECT emp_id, emp_name FROM employees_2025 MINUS SELECT emp_id, emp_name FROM employees_2024; -- 結果: 5(Eve)
MINUS は自動的に DISTINCT を適用する
MINUS の結果には重複行が含まれません。1 つ目の SELECT に同じ行が複数あっても、結果は 1 行になります。これは UNION と同じ動作です。重複を保持したい場合は Oracle 21c の
MINUS の結果には重複行が含まれません。1 つ目の SELECT に同じ行が複数あっても、結果は 1 行になります。これは UNION と同じ動作です。重複を保持したい場合は Oracle 21c の
MINUS ALL を使います。MINUS の順序で結果が変わる
・
差分の方向を間違えるとデータが逆になるので注意してください。
A MINUS B と B MINUS A は結果が異なります。・A MINUS B: A にあって B にない行・
B MINUS A: B にあって A にない行差分の方向を間違えるとデータが逆になるので注意してください。
MINUS vs NOT EXISTS vs NOT IN vs LEFT JOIN
「テーブル A にあるがテーブル B にないデータ」を取得する方法は MINUS 以外にもあります。それぞれの特徴を比較します。
SQL(4 つの書き方を比較)
-- (1) MINUS
SELECT emp_id FROM employees_a
MINUS
SELECT emp_id FROM employees_b;
-- (2) NOT EXISTS
SELECT a.emp_id FROM employees_a a
WHERE NOT EXISTS (
SELECT 1 FROM employees_b b WHERE b.emp_id = a.emp_id
);
-- (3) NOT IN
SELECT emp_id FROM employees_a
WHERE emp_id NOT IN (
SELECT emp_id FROM employees_b
);
-- (4) LEFT JOIN + IS NULL
SELECT a.emp_id
FROM employees_a a
LEFT JOIN employees_b b ON a.emp_id = b.emp_id
WHERE b.emp_id IS NULL;
| 方式 | 重複除去 | NULL 安全 | 複数列比較 | パフォーマンス | 可読性 |
|---|---|---|---|---|---|
| MINUS | 自動(DISTINCT) | 安全(NULL 同士=等値) | 全列を自動比較 | 中〜大(ソート発生) | 最もシンプル |
| NOT EXISTS | なし(元の重複を保持) | 安全 | 結合条件で指定 | 大量データで最速(インデックス活用) | 普通 |
| NOT IN | なし | 危険(NULL があると全行消える) | 単一列のみ | 中 | シンプル |
| LEFT JOIN IS NULL | なし | 安全 | 結合条件で指定 | 大(インデックス活用) | やや冗長 |
NOT IN は NULL に注意
NOT IN (1, 2, NULL) は全行が FALSE になり、結果が 0 行になります。これは x <> 1 AND x <> 2 AND x <> NULL と等価で、x <> NULL が常に UNKNOWN を返すためです。サブクエリの結果に NULL が含まれる可能性がある場合は、NOT EXISTS を使ってください。MINUS も NULL を安全に扱えます。使い分けの判断基準
・2 つの SELECT 全体の差分を手軽に確認 →
・特定の列(主キーなど)で存在チェック + 他の列も取得 →
・大量データでパフォーマンス重視 →
・重複行を保持したい →
・2 つの SELECT 全体の差分を手軽に確認 →
MINUS(最もシンプル)・特定の列(主キーなど)で存在チェック + 他の列も取得 →
NOT EXISTS(柔軟で高速)・大量データでパフォーマンス重視 →
NOT EXISTS(インデックスが効きやすい)・重複行を保持したい →
NOT EXISTS または LEFT JOIN(MINUS は DISTINCT 強制)MINUS と NULL の扱い
SQL(MINUS は NULL 同士を等しいとみなす)
-- テーブル A: (1, NULL), (2, 'X') -- テーブル B: (1, NULL), (3, 'Y') SELECT id, name FROM table_a MINUS SELECT id, name FROM table_b; -- 結果: (2, 'X') のみ -- (1, NULL) は両方にあるため除外される -- → MINUS は NULL 同士を「等しい」として扱う
MINUS の NULL 扱いは = 演算子と異なる
通常の
通常の
= 演算子では NULL = NULL は UNKNOWN(FALSE 扱い)ですが、MINUS(集合演算)では NULL 同士を等しいとして扱います。これは DISTINCT と同じ動作です。NULL を含むデータの差分比較では、MINUS が最も直感的な結果を返します。EXCEPT と MINUS ALL(Oracle 21c 以降)
SQL(EXCEPT: SQL 標準の MINUS)
-- Oracle 21c 以降: EXCEPT が使える(MINUS と同じ動作) SELECT emp_id FROM employees_a EXCEPT SELECT emp_id FROM employees_b; -- MINUS と完全に同じ結果(SQL 標準準拠)
SQL(MINUS ALL / EXCEPT ALL: 重複を保持)
-- Oracle 21c 以降: 重複を保持した差分 -- テーブル A: (1), (1), (1), (2) -- テーブル B: (1) -- MINUS: 重複除去 → 結果: (2) -- MINUS ALL: 重複保持 → 結果: (1), (1), (2) -- A に (1) が 3 行、B に (1) が 1 行 → 差分 2 行 + (2) 1 行 SELECT id FROM table_a MINUS ALL SELECT id FROM table_b;
| 演算子 | 重複除去 | Oracle バージョン | SQL 標準 |
|---|---|---|---|
| MINUS | あり(DISTINCT 相当) | 全バージョン | Oracle 独自(21c 以降は EXCEPT も可) |
| EXCEPT | あり(DISTINCT 相当) | 21c 以降 | SQL 標準 |
| MINUS ALL | なし(重複を保持) | 21c 以降 | Oracle 独自 |
| EXCEPT ALL | なし(重複を保持) | 21c 以降 | SQL 標準 |
集合演算 3 種の比較(UNION / INTERSECT / MINUS)
| 演算子 | 動作 | 数学的な意味 |
|---|---|---|
| UNION | 両方の結果を結合(重複除去) | 和集合(A + B) |
| UNION ALL | 両方の結果を結合(重複保持) | ― |
| INTERSECT | 両方に共通する行のみ | 積集合(A かつ B) |
| MINUS | A にあって B にない行のみ | 差集合(A – B) |
SQL(3 つの集合演算を同時に確認)
-- A = {1, 2, 3, 4}
-- B = {3, 4, 5, 6}
-- UNION: {1, 2, 3, 4, 5, 6}
-- INTERSECT: {3, 4}
-- A MINUS B: {1, 2}
-- B MINUS A: {5, 6}
MINUS の使用ルールと注意点
| ルール | 説明 | 違反した場合 |
|---|---|---|
| 列数の一致 | 両方の SELECT の列数が同じでなければならない | ORA-01789: query block has incorrect number of result columns |
| データ型の互換性 | 対応する列のデータ型が互換性を持つ必要がある | ORA-01790: expression must have same datatype as corresponding expression |
| ORDER BY の位置 | ORDER BY は最後の SELECT の後にのみ記述可能 | コンパイルエラー |
| 列名は最初の SELECT | 結果の列名は 1 つ目の SELECT の列名が使われる | ― |
SQL(ORDER BY の正しい書き方)
-- NG: 各 SELECT に ORDER BY を書くとエラー -- SELECT emp_id FROM table_a ORDER BY emp_id -- NG -- MINUS -- SELECT emp_id FROM table_b; -- OK: 最後にまとめて ORDER BY SELECT emp_id, emp_name FROM employees_a MINUS SELECT emp_id, emp_name FROM employees_b ORDER BY emp_id; -- MINUS の結果全体に適用
実務パターン集
パターン(1): 2 つのテーブルの差分レコードを検出
SQL(テーブル差分の検出)
-- 本番テーブルとバックアップテーブルの差分を検出 -- 本番にあってバックアップにないレコード(追加分) SELECT * FROM employees MINUS SELECT * FROM employees_backup; -- バックアップにあって本番にないレコード(削除分) SELECT * FROM employees_backup MINUS SELECT * FROM employees;
パターン(2): マスタの不整合チェック
SQL(マスタ不整合の検出)
-- orders テーブルに存在する customer_id で -- customers マスタに存在しないもの(孤立データ) SELECT customer_id FROM orders MINUS SELECT customer_id FROM customers; -- 逆: マスタにあるが注文が 1 件もない顧客 SELECT customer_id FROM customers MINUS SELECT customer_id FROM orders;
パターン(3): データ移行の検証
SQL(移行元と移行先の差分確認)
-- 移行元にあって移行先にないレコード = 移行漏れ SELECT emp_id, emp_name, salary FROM source_db.employees@db_link MINUS SELECT emp_id, emp_name, salary FROM target_employees; -- 件数が 0 なら移行完了 -- 件数がある場合は移行漏れを調査
パターン(4): 権限の差分確認
SQL(ユーザー間の権限差分)
-- USER_A に付与されていて USER_B に付与されていない権限 SELECT privilege, table_name FROM all_tab_privs WHERE grantee = 'USER_A' MINUS SELECT privilege, table_name FROM all_tab_privs WHERE grantee = 'USER_B';
パターン(5): 双方向差分を一度に取得
SQL(双方向差分を UNION ALL で統合)
-- A にあって B にない + B にあって A にない を一度に取得
SELECT 'A_ONLY' AS diff_type, emp_id, emp_name
FROM (
SELECT emp_id, emp_name FROM employees_a
MINUS
SELECT emp_id, emp_name FROM employees_b
)
UNION ALL
SELECT 'B_ONLY' AS diff_type, emp_id, emp_name
FROM (
SELECT emp_id, emp_name FROM employees_b
MINUS
SELECT emp_id, emp_name FROM employees_a
)
ORDER BY emp_id;
パフォーマンスの注意点
| 注意点 | 理由 | 対処法 |
|---|---|---|
| MINUS は内部でソートが発生する | 重複除去のために両方の結果をソートして比較する | 大量データでは NOT EXISTS(インデックス活用)を検討 |
| 全列比較のコスト | SELECT * で MINUS すると全列を比較するためコストが高い | 必要な列(主キーなど)だけ SELECT して MINUS する |
| MINUS を 3 つ以上チェーンする | 各 MINUS ごとにソートが発生しコストが累積 | 可能なら 1 回の NOT EXISTS に書き換える |
SQL(パフォーマンス改善: 全列 vs 主キーのみ)
-- 遅い: 全列を比較
SELECT * FROM employees_a
MINUS
SELECT * FROM employees_b;
-- 速い: 主キーだけで差分を検出 → 必要なら JOIN で他の列を取得
SELECT a.*
FROM employees_a a
WHERE a.emp_id IN (
SELECT emp_id FROM employees_a
MINUS
SELECT emp_id FROM employees_b
);
よくある質問
QMINUS と EXCEPT は何が違いますか?
A動作は全く同じです。
MINUS は Oracle 独自の構文、EXCEPT は SQL 標準の構文です。Oracle 21c 以降では両方使えます。他の RDBMS(PostgreSQL, SQL Server, MySQL 8.0+)では EXCEPT のみサポートされるため、移植性を考慮する場合は EXCEPT を使うのが安全です。QMINUS の結果に重複行は含まれますか?
Aいいえ。MINUS は自動的に DISTINCT(重複除去)を適用します。1 つ目の SELECT に同じ行が複数あっても、結果には 1 行だけ含まれます。重複を保持したい場合は Oracle 21c の
MINUS ALL を使うか、NOT EXISTS に書き換えてください。QMINUS で NULL を含む行はどう扱われますか?
AMINUS(集合演算)では NULL 同士を等しいとして扱います。両方の SELECT に
(1, NULL) がある場合、差分として残りません。これは通常の = 演算子(NULL = NULL は UNKNOWN)とは異なる動作です。QNOT IN で結果が 0 行になります。MINUS では正しく返ります
A
NOT IN のサブクエリの結果に NULL が 1 行でも含まれると、全行が除外されて結果が 0 行になります。これは SQL の仕様です(x <> NULL は常に UNKNOWN)。MINUS は NULL を安全に扱えるため、NULL を含むデータでは MINUS を使ってください。または NOT EXISTS も NULL 安全です。QMINUS の結果に ORDER BY を付けるには?
A
各 SELECT の中に個別に ORDER BY を書くことはできません。
ORDER BY は最後の SELECT の後に記述します。SELECT ... FROM table_a
MINUS
SELECT ... FROM table_b
ORDER BY column;各 SELECT の中に個別に ORDER BY を書くことはできません。
QMINUS と NOT EXISTS はどちらが速いですか?
Aデータ量とインデックスの有無によります。少量データでは MINUS の方がシンプルで十分高速です。大量データ(数十万行以上)では NOT EXISTS の方が高速なことが多いです。NOT EXISTS は結合キーのインデックスを活用できるのに対し、MINUS は両方の結果をソートして比較するためです。実行計画(EXPLAIN PLAN)で確認するのが確実です。
まとめ
MINUS 演算子の要点をまとめます。
| やりたいこと | 推奨方法 |
|---|---|
| A にあって B にないデータを取得 | SELECT … FROM A MINUS SELECT … FROM B |
| 重複を保持した差分(21c 以降) | MINUS ALL |
| 他 RDBMS にも移植可能な書き方(21c 以降) | EXCEPT |
| 特定の列で差分チェック + 他の列も取得 | NOT EXISTS(柔軟性が高い) |
| 大量データでの差分検出 | NOT EXISTS(インデックスが効きやすい) |
| 双方向差分を一度に取得 | (A MINUS B) UNION ALL (B MINUS A) |
| マスタの孤立データを検出 | SELECT key FROM child MINUS SELECT key FROM master |
| データ移行の検証 | SELECT * FROM source MINUS SELECT * FROM target(0 件なら完了) |
UNION の使い方は「UNION を使って複数の結果を結合する方法」も併せて参照してください。

