- Day22 前半
- 「EXISTS / IN は“あるかどうか”を聞くためのサブクエリ専用キーワード」
- まずは「何をしたいか」のイメージ
- 「注文を1件以上持っているユーザーだけを取りたい」
- EXISTS の基本形
- 「このユーザーに対応する行が orders に“1件でもあれば OK”」
- IN の基本形
- 「id が“このサブクエリの結果の中に含まれているか”」
- EXISTS と IN の“感覚的な違い”
- 「行の存在を聞くか、“値の集合”に含まれるかを聞くか」
- 「EXISTS は行、“IN は値”」という視点
- セキュリティの視点から見る EXISTS / IN
- 「“存在するかどうかだけ知りたい”というのは、情報を絞るうえで強い」
- Day22 前半のまとめ
Day22 前半
「EXISTS / IN は“あるかどうか”を聞くためのサブクエリ専用キーワード」
Day21 では、
SELECT の中にサブクエリを入れて「1行ごとの追加情報」をくっつけました。
Day22 のテーマは、
WHERE 句で使うサブクエリ、特に
EXISTS
IN
の2つです。
どちらも一言でいうと、
「別のテーブルに“その人(その値)が存在するか”を条件にする」
ための仕組みです。
JOIN でも似たことはできますが、
EXISTS / IN を使うと、
「存在チェック」という意図がとてもはっきりした SQL になります。
まずは「何をしたいか」のイメージ
「注文を1件以上持っているユーザーだけを取りたい」
次のようなテーブルを用意します。
users
id | name
---+-----------
1 | 山田太郎
2 | 佐藤花子
3 | 鈴木一郎
orders
id | user_id | amount
---+---------+-------
1 | 1 | 1200
2 | 1 | 3000
3 | 2 | 500
ここで欲しいのは、
「少なくとも1件は注文を持っているユーザーだけ」
です。
結果としてはこうなります。
id | name
---+-----------
1 | 山田太郎
2 | 佐藤花子
id=3 の鈴木一郎は、
orders に1件も行がないので除外したい、ということです。
これを実現する典型的な書き方が、
EXISTS と IN です。
EXISTS の基本形
「このユーザーに対応する行が orders に“1件でもあれば OK”」
EXISTS を使うと、こう書けます。
SELECT
u.id,
u.name
FROM users u
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
);
SQLここでのポイントを分解します。
WHERE 句に EXISTS (サブクエリ) と書いている
サブクエリの中で、外側の u.id を参照している(相関サブクエリ)
サブクエリの SELECT では SELECT 1 としている(何を SELECT してもよい)
EXISTS の意味は、
「このサブクエリが、1行でも返せば TRUE
1行も返せなければ FALSE」
です。
つまりこのクエリは、
users の1行ごとに、
「orders に user_id = u.id の行が1件でもあるか?」
をサブクエリでチェックし、
あればそのユーザーを残す、なければ除外する
という動きをします。
ここで重要なのは、
サブクエリの中身は「行があるかどうか」だけが重要で、
SELECT している値(1でも o.id でも)はどうでもいい
ということです。
だから慣れている人は、
「とにかく 1 を SELECT しておく」
という書き方をよくします。
IN の基本形
「id が“このサブクエリの結果の中に含まれているか”」
同じ条件は、IN でも書けます。
SELECT
u.id,
u.name
FROM users u
WHERE u.id IN (
SELECT o.user_id
FROM orders o
);
SQLIN の意味は、
「u.id が、サブクエリの結果集合の中に含まれているか?」
です。
サブクエリ
SELECT o.user_id
FROM orders o
SQLは、orders に存在する user_id の一覧を返します。
その一覧の中に u.id が含まれていれば TRUE、
含まれていなければ FALSE です。
結果として、
orders に user_id が出てくるユーザーだけが残る
→ 注文を1件以上持っているユーザーだけが残る
という動きになります。
EXISTS 版と IN 版は、
このケースでは同じ結果になります。
EXISTS と IN の“感覚的な違い”
「行の存在を聞くか、“値の集合”に含まれるかを聞くか」
どちらも「存在チェック」ですが、
頭の中のイメージは少し違います。
EXISTS
→ 「この条件を満たす行が、向こうのテーブルに1件でもある?」
IN
→ 「この値は、“あっちの SELECT が返した値の集合”の中に入ってる?」
さっきの例に当てはめると、
EXISTS
→ 「このユーザーに対応する注文行がある?」
IN
→ 「このユーザーの id は、“注文テーブルに出てくる user_id の一覧”に含まれている?」
という違いです。
どちらを使っても書ける場面は多いですが、
「何を聞いているのか」を自分の中で言葉にしてみると、
SQL の読み書きが一気に楽になります。
「EXISTS は行、“IN は値”」という視点
もう少しだけ整理します。
EXISTS は、
サブクエリが「行を返すかどうか」を見ています。
サブクエリが 1 行でも返せば TRUE
0 行なら FALSE
なので、
サブクエリの SELECT 句は何でもよく、SELECT 1 と書くのが定番です。
一方 IN は、
サブクエリが返す「値の集合」を見ています。
u.id IN (サブクエリ) の場合、
サブクエリは「1列だけ」を返す必要があります。
その 1 列の値たちの中に u.id が含まれているかどうか、
というチェックになるからです。
この違いを押さえておくと、
「この場面は行の存在を聞きたいから EXISTS」
「この場面は値の集合に含まれるかを聞きたいから IN」
と、自然に選べるようになっていきます。
セキュリティの視点から見る EXISTS / IN
「“存在するかどうかだけ知りたい”というのは、情報を絞るうえで強い」
EXISTS / IN は、
「あるかどうか」だけを条件にする道具です。
これはセキュリティの観点から見ると、
「余計な情報を出さずに、必要な判定だけを行う」
という意味で、とても良い性質を持っています。
例えば、
「このユーザーに、管理者権限のロールが付いているか?」
を判定したいとき、
ロールの一覧を全部 SELECT してアプリ側でチェックする
のではなく、
EXISTS で「該当する行が1件でもあるか」を聞くだけ
にしておけば、
アプリ側に余計な情報を渡さずに済みます。
もちろん、権限チェックはアプリ側の設計も重要ですが、
「存在するかどうかだけを DB に聞く」
というパターンを持っておくと、
“必要以上の情報を引き出さない”クエリ を書きやすくなります。
Day22 前半のまとめ
EXISTS は「このサブクエリが1行でも返せば TRUE」という“行の存在チェック”用のキーワード。
IN は「左側の値が、サブクエリが返した値の集合の中に含まれているか」を調べる“値の集合チェック”用のキーワード。
「注文を1件以上持っているユーザーだけ」を取りたい、のような場面では、EXISTS でも IN でも書けるが、頭の中のイメージは少し違う。
EXISTS ではサブクエリの SELECT 句は何でもよく(SELECT 1 が定番)、IN ではサブクエリは1列だけを返す必要がある。
「あるかどうかだけを DB に聞く」というスタイルは、余計な情報を引き出さずに判定だけ行えるため、セキュリティ的にも良い設計につながる。
後半では、
EXISTS と IN の書き分け(パフォーマンスや NULL の扱いの違い)
NOT EXISTS / NOT IN の注意点
JOIN との比較と、「どれを選ぶか」の判断軸
まで踏み込んで、EXISTS / IN を“なんとなく知っている”から“意図して選べる”レベルに持っていきます。
