MySQL | SQLite経験者向け、30日で習得するMySQL:実務SQL力 - Day9 サブクエリ最適化

SQL MySQL
スポンサーリンク

Day9 前半のゴール

「EXISTS と IN を“なんとなく同じ”から卒業する」

今日はサブクエリの中でも、実務で頻出のテーマ
EXISTS と IN
を扱います。

多くの人が最初こう思います。

「EXISTS でも IN でも、書き方が違うだけでしょ?」

ところが、
パフォーマンス・意味・書きやすさの面で、
ちゃんと使い分けられるとSQLの質が一段上がります。

Day9 前半のゴールは、

EXISTS と IN の基本的な書き方を理解する
「どちらでも書けるけど、考え方が違う」ことを知る
“存在チェック”なら EXISTS という感覚を持つ

ここまで行ければ、後半の「どっちが速いか」「どう最適化されるか」が入ってきます。


まずは題材テーブルを決める

「users と orders で“注文があるユーザー”を取りたい」

いつもの2テーブルを使います。

users
ユーザー情報(id, name, email など)

orders
注文情報(id, user_id, total など)

やりたいことはシンプルです。

「1件以上の注文を持っているユーザーだけを取得したい」

これを、EXISTS と IN の両方で書いてみます。


IN を使った書き方

「“値の集合に含まれているか”という発想」

まずは IN からいきます。

SELECT
  u.id,
  u.name
FROM users u
WHERE u.id IN (
  SELECT o.user_id
  FROM orders o
);
SQL

このSQLは、

サブクエリで「orders に登場する user_id の集合」を作る
users.id がその集合の中に含まれているユーザーだけを残す

という意味です。

頭の中のイメージとしては、

orders から user_id を全部集めて
→ そのリストの中に users.id があるかどうかをチェック

という動きです。

SQLiteでも IN はよく使っていたと思いますが、
MySQLでも基本の考え方は同じです。


EXISTS を使った書き方

「“対応する行が1件でも存在するか”という発想」

同じ条件を、EXISTS で書き直してみます。

SELECT
  u.id,
  u.name
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.id
);
SQL

このSQLは、

users の各行について
「orders に user_id = u.id の行が1件でも存在するか?」をチェックする

という意味です。

サブクエリの中身は、

SELECT 1
FROM orders o
WHERE o.user_id = u.id

となっていますが、
ここで SELECT しているのが 1 であることに意味はありません。

EXISTS は「行が1件でも返ってくるかどうか」だけを見ていて、
返ってきた値の中身には興味がないからです。


IN と EXISTS の“考え方の違い”

「集合に属するか vs 対応する行が存在するか」

同じ「注文があるユーザー」を取るSQLでも、
IN と EXISTS では発想が違います。

IN の発想
「orders.user_id の集合を作って、その集合に users.id が含まれているかを見る」

EXISTS の発想
「users の1行ごとに、orders に対応する行が1件でもあるかを聞く」

結果としては同じユーザーが返ってきますが、
頭の中のモデルが違う、というのがポイントです。

この違いが、
パフォーマンスや書きやすさに影響してきます。


なぜ EXISTS で SELECT 1 と書くのか

「EXISTS は“行の有無”だけを見るから」

EXISTS のサブクエリで、よくこう書かれます。

WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.id
);
SQL

「1って何?idじゃなくていいの?」と感じるかもしれません。

答えはシンプルで、

EXISTS は「1行でも返ってくるかどうか」しか見ていない
→ 何をSELECTしても結果は同じ

だからです。

極端な話、こう書いても意味は同じです。

SELECT 999
FROM orders o
WHERE o.user_id = u.id
SQL

でも、慣習的に「中身はどうでもいいよ」という意思表示として、
SELECT 1 と書くことが多いです。

ここで押さえたいのは、

EXISTS は「存在チェック専用の構文」
→ 値そのものには興味がない

という性質です。


「どちらでも書ける」典型パターン

「“注文があるユーザー”は IN でも EXISTS でも書ける」

さっきの例をもう一度並べます。

IN 版:

SELECT
  u.id,
  u.name
FROM users u
WHERE u.id IN (
  SELECT o.user_id
  FROM orders o
);
SQL

EXISTS 版:

SELECT
  u.id,
  u.name
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.id
);
SQL

どちらも、

orders に1件以上の注文を持つユーザーだけを返す

という意味で、結果は同じです。

Day9 前半では、

「このパターンは IN でも EXISTS でも書ける」
「でも、EXISTS は“存在チェックしてる感”が強い」

という感覚を持っておけばOKです。


SQLite と MySQL での“サブクエリの温度差”

「SQLiteではJOINで書き直していたものを、MySQLでは素直に使ってよい」

SQLite では、
サブクエリの最適化がそこまで強くなかったり、
EXPLAINを見て細かくチューニングする機会が少なかったりして、

「IN や EXISTS より JOIN で書き直した方が分かりやすい」
と感じていたかもしれません。

MySQL では、

サブクエリの最適化がかなり進んでいる
EXISTS / IN / JOIN を内部で書き換えることもある

という世界観です。

Day9 では、
「EXISTS は存在チェックのための構文」
「IN は“集合に属するか”を見る構文」

という役割を意識しながら、
どちらで書くと意図が伝わりやすいか、を考えていきます。


EXISTS を選びたくなる典型的な場面

「“とにかく存在するかだけ知りたい”とき」

前半の締めとして、
「こういうときは EXISTS を選びたい」という典型を1つだけ挙げておきます。

例えば、

「不正アクセスの疑いがあるIPアドレスからのログインが1件でもあるユーザーを抽出したい」

というケース。

SELECT
  u.id,
  u.name
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM login_logs l
  WHERE l.user_id = u.id
    AND l.ip = '192.0.2.123'
);
SQL

このとき、欲しいのは

「そのIPからのログインが何件あるか」ではなく
「1件でもあるかどうか」

です。

こういう「存在チェック」のとき、
EXISTS は読み手にとっても意図が分かりやすく、
MySQLにとっても最適化しやすい形になります。


Day9 前半のまとめ

IN は「サブクエリが返す“値の集合”に、左側の値が含まれているか」を見る構文であり、WHERE u.id IN (SELECT o.user_id FROM orders o) のように書くと、「orders に登場する user_id のリストの中に users.id があるユーザーだけを残す」という意味になる。
EXISTS は「サブクエリが1行でも返すかどうか(存在するかどうか)」だけを判定する構文であり、WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id) のように書くと、「users の各行について、orders に user_id = u.id の行が1件でも存在するユーザーだけを残す」という意味になる。
同じ「注文があるユーザー」を取る場合でも、IN は“集合に属するか”という発想、EXISTS は“対応する行が存在するか”という発想で書いており、結果は同じでも頭の中のモデルが違うため、「存在チェックをしている」と明示したいときには EXISTS の方が意図が伝わりやすい。
EXISTS のサブクエリで SELECT 1 と書くのは、EXISTS が「行の有無」しか見ておらず、返される値の中身には興味がないためであり、「不正IPからのログインが1件でもあるユーザー」のような“とにかく存在するかだけ知りたい”ケースでは、EXISTS を使うのが自然な選択になる。

後半では、
IN と EXISTS がパフォーマンス的にどう違うか、
MySQL が内部でどう最適化するか、
そして「JOINで書き直した方がよいケース」との境界線を、
具体的なパターンで掘り下げていきます。

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