SQLite | ゼロからはじめるSQL、30日で習得するSQLite:データ操作・設計 - Day22 サブクエリ②

SQL SQLite
スポンサーリンク

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
);
SQL

IN の意味は、

「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 を“なんとなく知っている”から“意図して選べる”レベルに持っていきます。

タイトルとURLをコピーしました