IN と EXISTS の使い分け
busho部署
| busho_id | busho_name |
|---|---|
| 1 | 経営 |
| 2 | 開発 |
| 6 | 法務✕ |
| … | |
EXISTS(社員が 1 人でもいるか)社員がいる部署
| busho_id | busho_name |
|---|---|
| 1 | 経営 |
| 2 | 開発 |
| … | |
WHERE EXISTS (SELECT 1 FROM shain s WHERE s.busho_id = busho.busho_id)
- 取り消し線= 条件に合わないので結果に出てこない行
- 部署 7 行 → 社員がいる部署 6 行(1 行が消えます)
7 部署のうち 6 部署が残ります。消えるのは社員が 1 人もいない法務だけです。
「別の表に該当する行があるか」を聞く書き方は 2 つあります。 IN は値の集合に含まれるかを、EXISTS は行が 1 つでもあるかを聞きます。多くの場合どちらでも同じ結果になりますが、否定したときだけ結果が変わります。
未確認の製品があります そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database
未確認: IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01同じことを 2 通りで書ける
結論: 「社員がいる部署」は IN でも EXISTS でも書けて、結果は同じです。
- IN … busho_id IN (SELECT busho_id FROM shain) 値の集合を作って、その中にあるかを聞く- EXISTS … EXISTS (SELECT 1 FROM shain s WHERE s.busho_id = b.busho_id) 条件に合う行が 1 つでもあるかを聞く
EXISTS のほうが長く見えますが、外側の行との対応関係が条件の中に書いてあるぶん、何を聞いているかが読み取りやすい形です。この「外側の行を参照する」書き方を相関と呼びます。
速さについては、どちらが速いかは一概に言えません。多くの製品は両者を同じ実行計画に書き換えるので、まずは読みやすいほうを選んでください。
SELECT b.busho_nameどの列を出すかFROM busho b外側は部署の表WHERE EXISTS (行が 1 つでもあれば真SELECT 1 FROM shain s内側は社員の表WHERE s.busho_id = b.busho_id)★外側の行と結びつける(相関)
最後の行がこの書き方の心臓部です。ここを書き忘れても SQL としては通ってしまうため、結果を見るまで気づけません。
SELECT b.busho_name
FROM busho b
WHERE EXISTS (SELECT 1
FROM shain s
WHERE s.busho_id = b.busho_id)
ORDER BY b.busho_id;| busho_name |
|---|
| 経営 |
| 開発 |
| 営業 |
| 管理 |
| 基盤 |
| マーケ |
(6 行)
6 部署です。社員が 1 人もいない法務だけが出ません。
SELECT busho_name
FROM busho
WHERE busho_id IN (SELECT busho_id FROM shain)
ORDER BY busho_id;| busho_name |
|---|
| 経営 |
| 開発 |
| 営業 |
| 管理 |
| 基盤 |
| マーケ |
(6 行)
同じ 6 部署です。社員表の busho_id には所属未設定ぶんの NULL が入っていますが、否定していない IN は NULL があっても平気です。
02EXISTS が見ているのは行の有無だけ
結論: EXISTS は内側の SELECT が何を返すかを一切見ません。 行が 1 つでもあれば真、0 行なら偽です。
そのため SELECT 1 と書くのが慣用です。SELECT * でも SELECT name でも結果は変わりません。「値は使わないので、いちばん短いものを置いておく」という意思表示になります。
次の例では内側で文字列を返していますが、結果は SELECT 1 のときとまったく同じです。
⚠️ ここが IN との構造的な違いです。IN は内側が返した値そのものを使うので、内側は必ず 1 列でなければなりません。EXISTS にはその制約がありません。
SELECT b.busho_name
FROM busho b
WHERE EXISTS (SELECT 'なんでもよい'
FROM shain s
WHERE s.busho_id = b.busho_id)
ORDER BY b.busho_id;| busho_name |
|---|
| 経営 |
| 開発 |
| 営業 |
| 管理 |
| 基盤 |
| マーケ |
(6 行)
6 部署です。SELECT 1 のときと 1 行も変わりません。内側の値は捨てられています。
03⚠️ よくある間違い:相関を書き忘れると全部通る
結論: EXISTS の中で外側の行を参照し忘れると、条件が「社員表に行があるか」だけになり、全行が通ります。
内側だけを見ると SELECT 1 FROM shain は 18 行返るので、常に真です。したがって外側の部署は1 つも絞られず 7 部署すべてが返ります。
エラーになりません。 SQL として完全に正しいからです。件数を数えないと気づけません。
さらに危ないのが否定側で、NOT EXISTS で同じ書き忘れをすると常に偽になり、結果が 0 行になります。「該当なし」という、もっともらしい答えが返ります。
見分け方は 1 つ。 EXISTS の中に外側の表の列が出てくるかを目で確かめてください。出てこなければ、それは相関していません。
SELECT b.busho_name
FROM busho b
WHERE EXISTS (SELECT 1 FROM shain s)
ORDER BY b.busho_id;| busho_name |
|---|
| 経営 |
| 開発 |
| 営業 |
| 管理 |
| 基盤 |
| 法務 |
| マーケ |
(7 行)
社員が 1 人もいない法務まで出ています。内側に b.busho_id が出てこないことに注目してください。
SELECT b.busho_name
FROM busho b
WHERE NOT EXISTS (SELECT 1 FROM shain s)
ORDER BY b.busho_id;| busho_name |
|---|
(0 行)
1 行も返りません。「社員が 1 人もいない部署は無い」と読めてしまいますが、実際には法務が該当します。
04⚠️ よくある間違い:否定するときは NOT EXISTS を選ぶ
結論: 「該当が無い行」を出したいときは NOT EXISTS を使ってください。 NOT IN は NULL 1 つで結果が 0 行になります。
社員表の busho_id には、所属が未設定の社員 2 人ぶんの NULL が入っています。この列を NOT IN に渡すと、エラーも警告も出ずに 0 行が返ります。
仕組みは NOT IN が <> AND <> AND … に展開されることによるもので、詳しくは下の「関連するトピック」の IN のページで扱っています。ここで押さえるべきは選択の基準です。
- 否定するなら
NOT EXISTS… NULL の影響を受けない - 否定しないなら好きなほう …
INでもEXISTSでも同じ結果になる
NOT IN を使ってよいのは、その列に NULL が入りえないと分かっているときだけです。「今は入っていない」ではなく「制約で入らない」まで確かめてください。
SELECT b.busho_name
FROM busho b
WHERE NOT EXISTS (SELECT 1
FROM shain s
WHERE s.busho_id = b.busho_id)
ORDER BY b.busho_id;| busho_name |
|---|
| 法務 |
(1 行)
法務が 1 行返ります。これが期待する答えです。
SELECT busho_name
FROM busho
WHERE busho_id NOT IN (SELECT busho_id FROM shain);| busho_name |
|---|
(0 行)
同じことを聞いているのに 1 行も返りません。社員表の busho_id に NULL が混ざっているためです。
05結合との使い分け
結論: 「あるかどうか」だけを知りたいなら EXISTS、相手の列も出したいなら結合です。
EXISTS は行を増やしません。外側の 1 行に対して内側が何行一致しても、結果はその 1 行のままです。
一方、内部結合で同じことを書くと、一致した数だけ行が増えます。「社員がいる部署」を結合で出すと、部署が社員の人数ぶん重複します。DISTINCT で潰すこともできますが、それは増やしてから減らしているので、意図が読み取りにくくなります。
- 存在の確認だけ →
EXISTS(行数が変わらない) - 相手の列も欲しい → 結合(行が増えるのは当然)
次の例は外部結合で人数を数えたものです。法務が 0 人であることが数字で確かめられます。
SELECT b.busho_name,
count(s.shain_id) AS ninzu
FROM busho b
LEFT JOIN shain s ON s.busho_id = b.busho_id
GROUP BY b.busho_id, b.busho_name
ORDER BY b.busho_id;| busho_name | ninzu |
|---|---|
| 経営 | 1 |
| 開発 | 4 |
| 営業 | 4 |
| 管理 | 3 |
| 基盤 | 2 |
| 法務 | 0 |
| マーケ | 2 |
(7 行)
法務が 0 人だと数字で分かります。EXISTS では「いる/いない」しか分かりません。目的で使い分けてください。
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
関連するトピック
- IN 述語NOT IN が 0 行になる仕組みを詳しく扱います。
- 相関副問い合わせEXISTS が使っている「外側の行を参照する」書き方です。
- スカラー副問い合わせ1 行 1 列を返す副問い合わせの使い方です。
- LEFT / RIGHT OUTER JOIN相手がいない行を残したいときの別のやり方です。
根拠(一次情報)
- PostgreSQL 18 マニュアル: 副問い合わせ式(EXISTS / IN)一次情報・確認 2026-08-03
- Microsoft SQL Server: 副問い合わせ(EXISTS を使った存在チェック)一次情報・確認 2026-08-03
- MySQL 8.4 リファレンスマニュアル: EXISTS / NOT EXISTS を使った副問い合わせ一次情報・確認 2026-08-03