SQL EXISTS: 関連レコードで行を絞り込む
SQL EXISTS は、相関サブクエリが一致する行を 1 件でも見つけると true を返します。JOIN のように結果を重複させず、関連する子テーブル行がある(またはない)親行だけを絞り込むときに使います。クエリプランナは EXISTS をセミ結合として実行することが多く、最初の一致が見つかった時点でサブクエリの探索を打ち切れます。
行の存在で絞り込む SQL EXISTS の例
出力:
ここに出力が表示されます...
出力:
+------+
| name |
+------+
| Alma |
| Cara |
+------+
2 row(s)
なぜ Alma と Cara だけが返るのか
- 2 つのテーブルを作成します。
customersには 3 人、ordersにはcustomer_idで紐づく 3 件の注文が入っています。 WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id)は顧客行ごとにサブクエリを 1 回実行します。一致する注文が 1 件でもあれば、その顧客は条件を通過します。- サブクエリ内の
SELECT 1は慣例です。データベースは SELECT リストの値を使わず、行が存在するかどうかだけを判定します。 - 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 を優先します。