SQL EXISTS: 関連レコードで行を絞り込む

SQL EXISTS は、相関サブクエリが一致する行を 1 件でも見つけると true を返します。JOIN のように結果を重複させず、関連する子テーブル行がある(またはない)親行だけを絞り込むときに使います。クエリプランナは EXISTS をセミ結合として実行することが多く、最初の一致が見つかった時点でサブクエリの探索を打ち切れます。

行の存在で絞り込む SQL EXISTS の例

出力:

ここに出力が表示されます...

出力:

+------+
| name |
+------+
| Alma |
| Cara |
+------+
2 row(s)

なぜ Alma と Cara だけが返るのか

  1. 2 つのテーブルを作成します。customers には 3 人、orders には customer_id で紐づく 3 件の注文が入っています。
  2. WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id) は顧客行ごとにサブクエリを 1 回実行します。一致する注文が 1 件でもあれば、その顧客は条件を通過します。
  3. サブクエリ内の SELECT 1 は慣例です。データベースは SELECT リストの値を使わず、行が存在するかどうかだけを判定します。
  4. Ben には注文がないため、サブクエリは 0 行を返し EXISTS は false になります。Alma と Cara には少なくとも 1 件の注文があるので結果に含まれます。

INNER JOIN と違い、EXISTS は外側の行を重複させません。Alma には 2 件の注文がありますが、EXISTS が問うのは「一致が存在するか」であり「何件あるか」ではないため 1 回だけ表示されます。

NOT EXISTS と NOT IN: NULL の落とし穴

SQL NOT EXISTS(NOT EXISTS)は、NULL を含みうる列でアンチ結合するときに安全です。サブクエリが NULL を返すと、NULL との比較は unknown になるため、NOT IN は想定外の結果を返します。

-- orders に customer_id が NULL の行があるとする
-- NOT IN はすべての行を黙って除外する
SELECT name FROM customers
WHERE id NOT IN (SELECT customer_id FROM orders);

-- NOT EXISTS は正しく動作する
SELECT name FROM customers c
WHERE NOT EXISTS (
  SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

NOT IN は id NOT IN (1, 3, NULL)id <> 1 AND id <> 3 AND id <> NULL として扱います。最後の比較は常に unknown になるため、述語全体は true にならず、結果は 0 行になります。NOT EXISTS は値の等価比較ではなく行の存在を判定するため、この問題を回避できます。

SQL EXISTS でよくある間違い

相関条件の欠落

サブクエリを外側の行に相関させないと、EXISTS は定数テストになります。注文が 1 件でも存在すれば、すべての顧客が一致してしまいます。

-- 誤り: 外側の行と相関していない
SELECT c.name FROM customers c
WHERE EXISTS (SELECT 1 FROM orders);

-- 正しい: customer_id で相関させる
SELECT c.name FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);

存在確認だけなのに JOIN を使う

INNER JOIN は一致 1 件ごとに 1 行を返すため、注文が複数ある顧客は複数回表示されます。

SELECT c.name
FROM customers c
JOIN orders o ON o.customer_id = c.id;

orders 側の列が不要なら EXISTS を使います。

EXISTS と IN と JOIN の使い分け

アプローチ使う場面
EXISTS (subquery)関連行の存在確認。サブクエリが相関している
NOT EXISTS (subquery)アンチ結合。特に NULL を取りうる外部キーで有効
IN (subquery)小さく、NULL を含まない値集合との照合
INNER JOIN結果で両テーブルの列が必要
LEFT JOIN ... IS NULLアンチ結合の代替。クエリプランナによってはこちらが有利な場合もある

原則: 「関連行が存在するか」を問うなら EXISTS を使います。両側のデータが必要になったら JOIN に切り替えます。

パフォーマンスに関する注意点

EXISTS はセミ結合として計画されることが多いです。相関列(この例では orders.customer_id)にインデックスがあると、エンジンは顧客ごとに最初の一致行だけを探せるため、orders 全体を走査せずに済みます。NOT EXISTS も同じインデックスの恩恵を受けます。EXISTS が遅い場合は、実行計画と相関列のインデックス有無を確認してください。

FAQ

EXISTS は最初の一致で停止しますか?

論理上は、条件を満たす行が 1 件見つかった時点で結果は true になります。多くのデータベースはそれを最初の一致で停止するセミ結合として実装しますが、最終的な戦略はクエリプランナ次第です。実際の動作は実行計画で確認してください。

EXISTS は UPDATE や DELETE でも使えますか?

はい。UPDATE ... WHERE EXISTS (subquery)DELETE ... WHERE EXISTS (subquery) を使うと、関連行の存在に基づいて更新・削除対象を絞り込めます。SELECT で使うのと同じ相関条件を保つことで、各対象行が自分に関連する行だけを評価できます。

SQL で EXISTS と IN の違いは何ですか?

EXISTS は(通常は相関した)サブクエリが 1 行でも返すかを判定します。IN は 1 つの値を結果集合と比較します。EXISTS は外側クエリとの相関と相性がよく、多くのエンジンで最初の一致で停止できます。IN は小さなリテラル集合や非相関の所属判定に向いています。NULL を含みうる列でアンチ結合するなら、NOT IN より NOT EXISTS を優先します。