順番に読み進めながら学べます

EXISTS と相関サブクエリ

SQL の EXISTS と相関サブクエリを解説します。EXISTS / NOT EXISTS、NOT IN と NULL の落とし穴を EXISTS で安全に回避する書き方、IN との使い分けを社員・売上データで学べます。

本記事で使うデータ — employee / sales / department

EXISTS は「条件に合う行が 1 つでも存在するか だけ」を判定するサブクエリで、IN と似た使い方をします。

本記事では EXISTS / NOT EXISTS の基本、NOT IN と NULL の落とし穴を EXISTS で安全に回避する書き方、そして EXISTSIN の読みやすさの使い分けを順に押さえます。

題材は社員データ 3 表です。

employee(社員 30 名)・sales(売上明細 50 件)・department(部署 6 件)を使い、「売上のある社員 / ない社員」「社員のいない部署」を抽出します。

演習に入る前に、本記事で使う 3 つのテーブル — employee / sales / department — の 列定義データのサンプル を確認しておきます。

PRAGMA table_info(...) で 3 テーブルの列定義を確認してください。

SELECT * FROM 表名 LIMIT 5; で各テーブルの先頭 5 行をプレビューしてください。employee.dept_id に NULL が入る点も後の演習で扱います。

SQL エディタ

クエリを実行してください

EXISTS / NOT EXISTS — 行が存在するかだけを見る

WHERE EXISTS (SELECT ... ) は、サブクエリが 1 行でも返せば真、1 行も返さなければ偽になります。

サブクエリが何を SELECT するかは結果に影響しないため、慣習として SELECT 1 と書きます(値は使わず、行の有無だけを見るため)。

EXISTS の中で外側の表の列を参照する相関サブクエリにするのが基本形です。

「売上のある社員」は WHERE EXISTS (SELECT 1 FROM sales sale WHERE sale.emp_id = emp.emp_id)、「売上のない社員」はこれを否定した NOT EXISTS で取り出せます。

EXISTS は 1 行見つかった時点で判定を打ち切るため、存在チェックに向いた書き方です。

-- EXISTS: 売上のある社員
SELECT emp.emp_id, emp.name
FROM employee emp
WHERE EXISTS (
  SELECT 1 FROM sales sale WHERE sale.emp_id = emp.emp_id
)
ORDER BY emp.emp_id;
EXISTS / NOT EXISTS の判定
メインの社員サブクエリの売上判定Bob(emp.emp_id=2)売上 5 行ありEXISTS 真→ 残るDave(emp.emp_id=4)売上 0 行NOT EXISTS 真→ 残る
上のサンプルコードの動きです。メインのクエリが社員を 1 人取り出すたびに、サブクエリがその社員の売上 (sale.emp_id = emp.emp_id) が 1 行でもあるかを調べます。1 行でもあれば EXISTS が真、1 行も無ければ NOT EXISTS が真になります。

「売上明細に 1 件も登場しない社員(まだ売上を計上していない社員)の一覧を出したい」という要件を想定します。(正しく実行できれば解説が表示されます)

employee テーブルに別名 e を付け、emp_idname を取り出してください。

NOT EXISTS を使い、その社員の売上が sales1 行も存在しない 行に絞ってください。サブクエリは SELECT 1 FROM sales sale WHERE sale.emp_id = emp.emp_id の形で外側と相関させます。

emp_id の昇順で並べてください。

SQL エディタ

クエリを実行してください

NOT IN と NULL の落とし穴 — EXISTS は安全

「否定の絞り込み」を NOT IN で書くと、サブクエリ側に NULL が混ざったときに 結果が 0 行になる ことがあります。

x NOT IN (1, 2, NULL) は「x が 1 でなく、2 でなく、NULL でもない」と評価されますが、x <> NULL は真にならず常に NULL(不明)になるため、NOT IN 全体が真になりません。

詳しい理屈は サブクエリ ①(NOT IN と NULL の落とし穴) で解説しています。

employee.dept_id には NULL が混ざるので、「社員のいない部署」を WHERE dept_id NOT IN (SELECT dept_id FROM employee) で書くと、本来あるはずの Legal(社員が 1 人もいない部署)が消えて結果が 0 行になります。

NOT EXISTS は行の有無だけを見て NULL に影響されないため、この場面では NOT EXISTS が正しい結果を返します。

NOT IN × NULL の落とし穴と NOT EXISTS の安全性
NOT IN(NULL に弱い)NOT EXISTS(安全)dept_id NOT IN(SELECT dept_id FROM employee)NOT EXISTS(SELECT 1 FROM employee emp WHERE emp.dept_id = d.dept_id)NULL が混ざり結果 0 行(Legal が消える)Legal を正しく返す
サブクエリに NULL が混ざると NOT IN は真にならず結果が 0 行になります。NOT EXISTS は行の有無だけを見るため NULL の影響を受けません。
-- 落とし穴: NULL を含むサブクエリ + NOT IN は 0 行になる
SELECT dept_id, dept_name
FROM department
WHERE dept_id NOT IN (SELECT dept_id FROM employee);
-- → employee.dept_id に NULL があるため結果が 0 行

-- 安全: NOT EXISTS なら NULL の影響を受けない
SELECT d.dept_id, d.dept_name
FROM department d
WHERE NOT EXISTS (
  SELECT 1 FROM employee emp WHERE emp.dept_id = d.dept_id
);

「所属社員が 1 人もいない部署を出したい」という要件を想定します。employee.dept_id には NULL が混ざるため、NOT EXISTS で安全に書きます。

department テーブルに別名 d を付け、dept_iddept_name を取り出してください。

NOT EXISTS を使い、その部署に所属する社員が employee1 行も存在しない 行に絞ってください。サブクエリは SELECT 1 FROM employee emp WHERE emp.dept_id = d.dept_id の形で相関させます。

dept_id の昇順で並べてください。

SQL エディタ

クエリを実行してください

前の演習を NOT IN で書くとどうなるかを確かめます。誤りを体感するための演習なので、NOT IN のまま実行して 結果が 0 行になること を観察してください。

department テーブルから dept_iddept_name を取り出してください。

WHERE dept_id NOT IN (SELECT dept_id FROM employee) で「社員のいない部署」を出そうとしてください。

③ 実行し、結果が 0 行(Legal 部署が出てこない)になることを確認してください。実践 2 の NOT EXISTS 版と結果が違う理由を考えてみてください。

SQL エディタ

クエリを実行してください

EXISTS は読みやすさで使い分ける

EXISTS は行の有無だけを見るため NULL があっても安全ですが、相関サブクエリを書くぶん IN より長く見えることがあります。

単純な存在判定では IN の方が読みやすい場面もあり、実務では文脈に応じて使い分けます。

一方、NOT IN のように NULL で結果が壊れる否定や、行の有無だけを確実に判定したいときは EXISTS / NOT EXISTS が安全です。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1WHERE EXISTS (SELECT 1 FROM sales sale WHERE sale.emp_id = emp.emp_id) の説明として正しいものはどれですか。

Q2サブクエリ側に NULL が混ざりうるとき、否定の絞り込みを NOT IN で書くと起こりうる問題はどれですか。

Q3EXISTSIN と使い分ける判断として、適切なものはどれですか。