SQLの新しいレクチャーへようこそ!今日は一見地味だけどめっちゃ強力なオペレーター — EXISTS と NOT EXISTS を紹介するよ。まるで痕跡を残さずに「うん、オブジェクトあるよ」または「いや、ここ何もないよ」って即答してくれるスパイみたいなやつ。これらのオペレーターは直接データを返さないけど、論理的に正確なチェックをクエリでできるようにしてくれるんだ。
まずは基本から。EXISTS はサブクエリの結果にレコードが存在するかをチェックするオペレーター。サブクエリが1件でもレコードを返せば EXISTS は TRUE を返し、なければ FALSE になる。
SELECT 1
WHERE EXISTS (
SELECT *
FROM students
WHERE grade > 3.5
);
見ての通り、サブクエリのデータ自体には興味なくて、そういう行が「あるかどうか」だけを見てる。1件でも条件に合うレコードがあれば、クエリは 1 を返すよ。
EXISTS のシンタックス
EXISTS のシンタックスはシンプル:
SELECT カラム
FROM テーブル
WHERE EXISTS (
SELECT 1
FROM 別のテーブル
WHERE 条件
);
解説:
EXISTSの中のサブクエリはどんなクエリでもOK。- サブクエリの結果が
TRUEかFALSEかを決める。
例:4より高い成績の学生がいるか?
テーブル students を想像してみて:
| id | name | grade |
|---|---|---|
| 1 | Otto | 3.2 |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | 2.9 |
例えば、成績が4より高い学生がいるかチェックしたいときはこう書く:
SELECT '高得点の学生がいるよ!'
WHERE EXISTS (
SELECT 1
FROM students
WHERE grade > 4
);
結果:
高得点の学生がいるよ!
なぜ EXISTS は IN より速いの?
EXISTS の最大の強みは、最初の一致を見つけた時点でサブクエリの実行を止めること。つまり、データの存在チェックなら EXISTS はめっちゃ効率的なんだ。
例えば、students テーブルに何百万件もレコードがあっても、条件(grade > 4)に合う1件を見つけたら即終了。
NOT EXISTS の使い方
次は NOT EXISTS について話そう。これは EXISTS の逆バージョン。サブクエリが1件もレコードを返さないときに TRUE を返す。
例:成績がない(NULL)の学生を探す
例えば、成績がまだついてない学生がいるとする:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | NULL |
成績がない学生だけを選びたい。NOT EXISTS を使うと:
SELECT *
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM students
WHERE grade IS NOT NULL
AND id = s.id
);
結果:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 4 | Lina | NULL |
EXISTS と IN の比較
一見 EXISTS と IN は同じことをしてるように見える。でも実は違いがある。特に NULL が絡むと IN の挙動が予想外になったり、EXISTS が救世主になったりする。
例で見てみよう。
テーブル courses(受講できるコース):
| course_id | name |
|---|---|
| 1 | 数学 |
| 2 | 歴史 |
そして学生たち:
| student_id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
テーブル enrollments(誰がどのコースに登録したか):
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
誰かが登録しているコース名を選びたい。簡単そうだよね。
IN を使う場合:
SELECT name
FROM courses
WHERE course_id IN (
SELECT course_id
FROM enrollments
);
一見これでOKに見える。でも enrollments の courseid に NULL があると(Maria Chi の場合)、IN は…何も返さないことも!NULL がサブクエリを「未定義」にしてしまい、SQLは「もしかして NULL が探してる courseid かも?」って混乱するんだ。
EXISTS を使う場合:
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
);
でも EXISTS は「course_id が一致する行が1件でもある?」ってだけをチェック。NULL があっても気にしない。具体的な一致だけを探すから。
まとめ:サブクエリに NULL が混じる可能性があるなら、EXISTS を使った方が安心だよ。
リアルな課題例
テーブル students:
| id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
テーブル enrollments:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
例1. コースに登録した学生
どこかで登録したことがある学生(Maria Chi みたいな変なケースも含む)を探す:
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
結果:
Alex Lin
Anna Song
Maria Chi
enrollments に1件でも出てくれば、その学生は抽出される。course_id が不明でもOK。
例2. コース未登録の学生
今度は、まだどこにも登録してない学生を探す:
SELECT name
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
結果:
Dan Seth
Shadow Moon
この2人はまだお気に入りのコースが見つかってないみたい。もしくは登録忘れかも :)
例3. 5人以上登録しているコースの抽出
テーブル courses:
| course_id | name |
|---|---|
| 1 | 数学 |
| 2 | 歴史 |
| 3 | 生物学 |
| 4 | 哲学 |
テーブル enrollments:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
| 10 | NULL |
5人以上登録しているコースを探したい。ここで EXISTS は「このコースに5人以上の学生がいるグループがある?」って聞いてる感じ。
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
GROUP BY e.course_id
HAVING COUNT(*) > 5
);
結果:
数学
「数学」(course_id = 1)だけが6人登録されてる。他のコースはまだ人気イマイチ。
EXISTS と NOT EXISTS のよくあるミス
- サブクエリのシンタックスの誤解。サブクエリが外部テーブルをちゃんと参照してるか必ず確認しよう。
NULLチェックの忘れ。EXISTSを使っても、時にはNULLの扱いを明示する必要がある。- サブクエリのフィールドにインデックスがない。これがあるとクエリがめっちゃ遅くなることも。
今日はここまで!これで EXISTS と NOT EXISTS を使ったデータ存在チェックや、これらと IN の違いが分かったはず。次のレクチャーでは、SELECT でのサブクエリと集計データの扱いをさらに深掘りするよ。
GO TO FULL VERSION