EXISTSとJOINのパフォーマンス
より高速なパターンを選びます。
「EXISTSとJOINのパフォーマンス」はCoddyKit上の無料SQL Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSQL Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 SQL Academyコースには全4レッスンが含まれています。
ここでパフォーマンスが重要な理由
別のテーブルに関連する行が存在するかどうかを確認する場合、SQLにはEXISTS、IN、JOINなど、複数の方法があります。どれも正しい結果を返しますが、データ量、インデックス、データベースエンジンによってパフォーマンスは大きく異なることがあります。
このレッスンでは、それぞれの方法が内部でどのように動作するか、またどのような場合にどの方法を選ぶべきかを学びます。
サンプルテーブル
このレッスンでは、customersとordersという2つのテーブルを一貫して使用します。1人の顧客が持つ注文は0件の場合も複数件の場合もあります。これは、EXISTSとJOINのパターンを検証するのに適した、典型的な1対多の関係です。
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
total NUMERIC(10,2)
);
INSERT INTO customers (name) VALUES
('Alice'), ('Bob'), ('Carol'), ('Dave');
INSERT INTO orders (customer_id, total) VALUES
(1, 120.00), (1, 85.50), (3, 200.00);JOINを使う方法
顧客が少なくとも1件の注文を持つことを確認するには、INNER JOINを使用するのが一般的です。これは機能しますが、問題に注目してください。ある顧客に注文が5件あると、DISTINCTで重複がまとめられる前に、その顧客が結果セットに5回現れます。
この重複は、データベースにとって余分な処理です。データベースはJOINの完全な結果を作成してから重複を排除する必要があります。
SELECT DISTINCT c.id, c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;EXISTSを使う方法
EXISTSは、少なくとも1つの一致する行が存在するかという、はい・いいえの問いに答えます。エンジンは最初の一致を見つけた時点でスキャンを停止します。これを短絡評価と呼びます。
EXISTSは内部の行を実際に返すことがないため、重複は生成されず、DISTINCTも必要ありません。
SELECT c.id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);短絡評価が鍵
短絡評価とは、条件を満たす行が1行見つかると、サブクエリが直ちに停止することです。顧客の注文が1件でも10,000件でも、EXISTSが読み取るのは最初に一致するまでです。
JOINでは、存在するかどうかだけを知りたい場合でも、結果セットを作成するために一致するすべての行を読み取る必要があります。幅の広いテーブルで、親1行あたりの子行が多い場合、この差はすぐに大きくなります。
-- EXISTS stops after finding row #1
SELECT c.name
FROM customers c
WHERE EXISTS (
SELECT 1 -- 'SELECT 1' is conventional; the value does not matter
FROM orders o
WHERE o.customer_id = c.id
);
-- JOIN scans ALL matching order rows
SELECT DISTINCT c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;NOT EXISTSとLEFT JOIN ... IS NULLの比較
反対に、注文を持たない顧客を探す場合は、NOT EXISTSまたはLEFT JOIN ... WHERE IS NULLパターンを使用できます。どちらも一般的な方法ですが、通常はNOT EXISTSのほうが読みやすく、オプティマイザーもNOT EXISTSを優先することがよくあります。
-- NOT EXISTS
SELECT c.id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
-- LEFT JOIN ... IS NULL (equivalent result)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;インデックスの役割
EXISTSとJOINはどちらも、外部キー列にインデックスがあると大きな恩恵を受けます。orders.customer_idにインデックスがない場合、外側の各行についてordersの全テーブルスキャンが発生します。
このインデックスを追加することが、パフォーマンスを最も大きく改善する方法になることがよくあります。EXISTSとJOINのどちらを選ぶかよりも、大きな効果がある場合があります。
-- Create an index on the foreign key
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
-- Now both patterns use an index lookup instead of a full scan
EXPLAIN
SELECT c.name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);EXPLAINの出力を読む
EXPLAIN(クエリも実行するにはEXPLAIN ANALYZE)を使用すると、データベースがクエリをどのように実行するかを確認できます。次の点に注目してください。
- Index Scan — 良好です。インデックスが使用されています。
- 大きなテーブルに対するSeq Scan — 注意が必要な兆候です。インデックスが役立つ可能性があります。
- Hash Join / Nested Loop — 選択されたJOINアルゴリズムです。Nested Loopはインデックススキャンと相性がよい方法です。
EXPLAIN ANALYZE
SELECT c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
HAVING COUNT(o.id) > 0;JOINが適している場合
純粋に存在を確認する場合はEXISTSが力を発揮します。しかし、関連テーブルのデータ(注文合計や注文日など)も必要な場合は、JOINを使用する必要があります。EXISTSサブクエリの内部から列を返す方法はありません。
問いに合った方法を選んでください。「存在するか」を確認するならEXISTS、「両方のテーブルからデータを取得する」ならJOINです。
-- Need order data? JOIN is the only option.
SELECT c.name, o.total, o.id AS order_id
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
ORDER BY c.name;大規模な集合での IN と EXISTS
IN (subquery) は最初にサブクエリ全体を評価し、値のリストをメモリ上に作成してから、外側の各行をそのリストと照合します。数百万行になると、このリストによってメモリを使い果たす可能性があります。
EXISTS は行単位で評価され、短絡評価を行うため、内部クエリの結果セット全体を実体化することはありません。大規模な相関チェックでは、EXISTS のほうがほぼ常に IN より高速です。
-- IN builds the full list first
SELECT name
FROM customers
WHERE id IN (
SELECT customer_id FROM orders
);
-- EXISTS evaluates per-row and short-circuits
SELECT name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);使い分け早見表
適切なパターンを選ぶための簡単な目安を示します。
- EXISTS — 一致する行が存在するかどうかだけを確認したい場合、大きな子テーブルを扱う場合、アンチ結合には NOT EXISTS を使用します。
- JOIN — 関連テーブルの列が必要な場合、または両方のテーブルにまたがる集計を行う場合に使用します。
- IN — 短い静的な値のリスト(
WHERE status IN ('active', 'pending'))に使用し、大規模なサブクエリには使用しないでください。 - 外部キー列には必ずインデックスを作成してください。これは構文の選択よりも重要です。
理解度チェック
関連する行の存在を確認する場合に、EXISTS が INNER JOIN + DISTINCT より高速になる理由を最も適切に説明している文はどれですか。
レッスンのまとめ
このレッスンでは、パフォーマンスを意識した SQL で EXISTS と JOIN を使い分ける方法を学びました。
- EXISTS は短絡評価を行います — 最初の一致が見つかるとスキャンを停止するため、
DISTINCTを使わずに重複を避けられます。 - JOIN は一致するすべての行を返します — 関連テーブルのデータが必要な場合に使用します。親行だけが必要な場合は、
DISTINCTまたはGROUP BYを追加してください。 - NOT EXISTS は簡潔なアンチ結合パターンです。LEFT JOIN ... IS NULL でも同じ結果になりますが、より冗長です。
- 大規模なサブクエリでの IN は避けてください — 内部クエリの結果全体を実体化するため、
EXISTSのほうがメモリ効率に優れています。 - 外部キーにインデックスを作成してください — 選択する構文にかかわらず、この1つの対策で最大のパフォーマンス向上が得られることがよくあります。
- EXPLAIN / EXPLAIN ANALYZE を使用して実行計画を確認し、インデックスが使われていることを確かめてください。
よくある質問
「EXISTSとJOINのパフォーマンス」レッスンは無料ですか?
はい。「EXISTSとJOINのパフォーマンス」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、SQL Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 SQL Academyコースには全4レッスンが含まれています。
「EXISTSとJOINのパフォーマンス」で何を学びますか?
より高速なパターンを選びます。 ブラウザで直接実行するハンズオンコードでSQL Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
SQL Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのSQL Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「EXISTSとJOINのパフォーマンス」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このSQL Academyレッスンでコードを書いて実行できますか?
はい。すべてのSQL Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 相関サブクエリ
- EXISTSとNOT EXISTS
- IN、ANY、ALLの比較
- EXISTSとJOINのパフォーマンス