【2026年7月最新】SQL IN句完全ガイド|基本・NOT IN・サブクエリ・EXISTSとの比較・Claude Code活用
この記事の内容
SQLでデータを検索するとき「この3つの都市の顧客を取得したい」「この商品コードの一覧に含まれる注文を抽出したい」という場面で使うのがIN句です。ORを複数並べるより簡潔に書け、サブクエリと組み合わせることで強力なフィルタリングが実現できます。
この記事では、IN句の基本構文から、NOT IN・サブクエリとの組み合わせ・EXISTSとの違い・NULLが含まれる場合の落とし穴・パフォーマンスの考え方・Claude CodeによるSQL自動生成まで、実践コードとともに解説します。
01 BASIC SQL IN句の基本構文と使い方 WHERE col IN (値リスト)の基本形とデータ型別の使い方
IN句はWHERE句の中で使い、指定した値のリストのいずれかに一致する行を取得します。
-- IN句の基本構文
SELECT カラム名
FROM テーブル名
WHERE カラム名 IN (値1, 値2, 値3, ...);
-- 数値型のIN句
SELECT *
FROM orders
WHERE status_id IN (1, 2, 5);
-- status_idが1・2・5のいずれかの注文を取得
-- 文字列型のIN句(シングルクォートで囲む)
SELECT *
FROM customers
WHERE prefecture IN ('東京都', '大阪府', '神奈川県');
-- 3都府県の顧客を取得
-- 日付型のIN句
SELECT *
FROM sales
WHERE sale_date IN ('2026-07-01', '2026-07-15', '2026-07-19');
-- 複数カラムの条件と組み合わせ
SELECT order_id, customer_name, total_amount
FROM orders
WHERE status_id IN (1, 2)
AND total_amount >= 10000;
-- ステータスが1か2で、金額1万以上の注文
📚 用語解説
IN句の評価方法:WHERE col IN (v1, v2, v3) は内部的にcol = v1 OR col = v2 OR col = v3と同等に評価される。ただしINのリストは可読性が高く、最適化エンジンが効率的な実行計画を選択しやすい。IN句のリストはコンマ区切りの値の集合であり、重複した値は1つとして扱われる(SELECT * FROM t WHERE x IN (1, 1, 2) は x IN (1, 2) と同じ)。
| データ型 | 記述例 | 注意点 |
|---|---|---|
| 数値(INT) | WHERE id IN (1, 2, 3) | クォート不要 |
| 文字列(VARCHAR) | WHERE name IN ('田中', '山田') | シングルクォートで囲む |
| 日付(DATE) | WHERE dt IN ('2026-01-01', '2026-01-15') | ISO形式YYYY-MM-DD推奨 |
| BOOLEAN | WHERE active IN (TRUE, FALSE) | DBによって記述が異なる |
02 IN_VS_OR IN句 vs OR演算子の違いとどちらを使うべきか 可読性とパフォーマンスの観点からの使い分け
IN句とOR演算子は同じ結果を返しますが、可読性と保守性の観点からIN句が推奨されます。
-- OR演算子で書く場合(値が増えると読みにくい) SELECT * FROM orders WHERE status_id = 1 OR status_id = 2 OR status_id = 5 OR status_id = 8 OR status_id = 10; -- IN句で書く場合(すっきりと読みやすい) SELECT * FROM orders WHERE status_id IN (1, 2, 5, 8, 10); -- 結果は完全に同じ -- IN句のほうが: -- ① 書く量が少ない -- ② 値の追加・削除が簡単 -- ③ コードレビューで意図が伝わりやすい
モダンなRDBMS(MySQL, PostgreSQL, SQL Server等)では、IN句とORはクエリオプティマイザによって同等の実行計画に変換されます。かつては「IN句はORより遅い」という説もありましたが、現在のDBではほぼ同等です。値が3つ以上になる場合は可読性の観点からIN句を使うのが推奨です。
03 NOT_IN NOT IN句の使い方と注意点 NOT INはNULLが含まれると結果が全件ゼロになる重大な落とし穴
-- NOT IN句の基本的な使い方 -- 「リストに含まれない行を取得する」 SELECT * FROM orders WHERE status_id NOT IN (3, 4, 9); -- ステータスが3・4・9以外の注文を取得 -- 実際によく使うパターン -- 「削除済み・キャンセル以外のすべての注文」 SELECT order_id, customer_name, total_amount FROM orders WHERE status_id NOT IN (8, 9) -- 8=削除済み, 9=キャンセル ORDER BY created_at DESC;
これはSQLの最大の落とし穴の一つです。NOT IN (1, 2, NULL) は内部的にcol != 1 AND col != 2 AND col != NULLと評価されます。NULLとの比較(col != NULL)は常にNULL(UNKNOWN)を返すため、AND条件全体がUNKNOWNになり、どの行もWHERE条件をパスしません。結果: 0件が返る。サブクエリでNOT INを使う場合、サブクエリの結果にNULLが含まれると同様に0件になります。対策: NOT INの代わりにNOT EXISTSを使う、またはWHERE col IS NOT NULLをサブクエリに追加する。
-- NOT INのNULL問題の実例
-- 例: productsテーブルにcategory_idがNULLの行がある場合
-- NG: サブクエリがNULLを返すとproducts全件が0件になる
SELECT *
FROM orders
WHERE product_id NOT IN (
SELECT product_id FROM discontinued_products
-- discontinued_productsにNULLのproduct_idがあると全件ゼロ!
);
-- 対策1: サブクエリにIS NOT NULLを追加
SELECT *
FROM orders
WHERE product_id NOT IN (
SELECT product_id FROM discontinued_products
WHERE product_id IS NOT NULL -- NULLを除外
);
-- 対策2: NOT EXISTSを使う(NULLに安全)
SELECT *
FROM orders o
WHERE NOT EXISTS (
SELECT 1
FROM discontinued_products dp
WHERE dp.product_id = o.product_id
);
📚 用語解説
SQLにおける3値論理(TRUE/FALSE/UNKNOWN):SQLはTRUE/FALSEの2値論理ではなく、NULLを含む3値論理(TRUE/FALSE/UNKNOWN)を使う。NULLとの比較(col = NULL、col != NULL)はすべてUNKNOWNを返す(TRUEでもFALSEでもない)。WHERE句の条件がUNKNOWNの行は結果に含まれない(FALSEと同様に扱われる)。NULL値の存在確認はcol IS NULLまたはcol IS NOT NULLで行う(col = NULLは使えない)。
04 SUBQUERY IN句とサブクエリの組み合わせ 動的なリストをSELECTで生成してINに渡す強力なパターン
IN句の値リストを固定値でなくサブクエリ(SELECT文)で生成することで、動的なフィルタリングが可能になります。これはSQLの中でも最も頻繁に使われる強力なパターンの一つです。
-- IN句 + サブクエリの基本形
SELECT カラム
FROM テーブルA
WHERE カラム IN (
SELECT カラム FROM テーブルB
WHERE 条件
);
-- 実例1: 注文のある顧客だけを取得
SELECT customer_id, name, email
FROM customers
WHERE customer_id IN (
SELECT DISTINCT customer_id
FROM orders
WHERE created_at >= '2026-01-01' -- 今年注文した顧客のID
);
-- 実例2: 特定カテゴリの商品を購入した注文を取得
SELECT *
FROM orders
WHERE product_id IN (
SELECT product_id
FROM products
WHERE category_id = 5 -- カテゴリID=5の商品
);
-- 実例3: 集計を使ったサブクエリ
-- 「売上総額が10万円以上の顧客の詳細を取得」
SELECT *
FROM customers
WHERE customer_id IN (
SELECT customer_id
FROM orders
GROUP BY customer_id
HAVING SUM(total_amount) >= 100000
);
customersから
取得
ordersから
customer_idリスト生成
customersのcustomer_id
がリストにあるか
条件に合う顧客行
05 EXISTS IN vs EXISTS:どちらを選ぶべきか 実行計画・NULL安全性・相関サブクエリの観点での使い分け
📚 用語解説
EXISTS句:WHERE EXISTS (サブクエリ) は「サブクエリが1行以上結果を返すならTRUE」を意味する。INと異なり、EXISTSは相関サブクエリ(外側の行を参照するサブクエリ)と組み合わせて使うことが多い。SELECT 1(定数)をサブクエリのSELECT句に使うのは、EXISTSは存在確認しかしないためどの値を返しても良いから。NULLが含まれても正しく動作するためNOT INの代わりにNOT EXISTSを使うことが推奨される。
-- IN句で書く場合
SELECT *
FROM customers c
WHERE c.customer_id IN (
SELECT o.customer_id
FROM orders o
WHERE o.status_id = 1
);
-- EXISTS句で書く場合(相関サブクエリ)
SELECT *
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id -- 外側の行を参照(相関)
AND o.status_id = 1
);
-- どちらも同じ結果だが実行計画が異なる場合がある
-- 最新DBのオプティマイザはどちらも最適化するが、
-- NULLが含まれる場合の挙動が決定的に違う
| 比較軸 | IN (サブクエリ) | EXISTS |
|---|---|---|
| NULLへの安全性 | NOT INでNULL問題あり | NOT EXISTSはNULL安全 |
| サブクエリの実行 | 先にサブクエリを実行(非相関) | 外側の行ごとにサブクエリ実行(相関) |
| 結果が大きい場合 | IN句のリストが大きくなる | 早期打ち切りが効く場合あり |
| 結果が小さい場合 | 効率的なことが多い | 相関サブクエリは行ごとに実行 |
| 可読性 | 直感的で分かりやすい | 慣れが必要だが正確な意図を表現 |
| 推奨ケース | 小〜中規模のリスト | NOT EXISTS(NULLに安全)、相関条件が複雑な場合 |
MySQL 5.6+、PostgreSQL、SQL Serverなどの最新DBでは、クエリオプティマイザがIN句をEXISTSに変換したり、ハッシュ結合・ネステッドループ結合などを自動選択します。「INはEXISTSより遅い」は古い話で、現在はほぼ同等のケースが多いです。EXPLAIN(実行計画)で確認するのが確実です。ただしNOT INのNULL問題はパフォーマンスではなく正確性の問題のためNOT EXISTSを優先すべきです。
06 NULL_TRAP IN句にNULLが含まれる場合の落とし穴 NULLはINで一致しない・NOT INで全件ゼロになる動作の理解
-- IN句でのNULLの動作
-- テストデータ
CREATE TABLE test_items (id INT, name VARCHAR(50));
INSERT INTO test_items VALUES (1, 'りんご'), (2, NULL), (3, 'みかん');
-- NULLはINで「一致」にならない
-- name IN ('りんご', NULL, 'みかん') は
-- name = 'りんご' OR name = NULL OR name = 'みかん'
-- name = NULL はUNKNOWN(TRUEにならない)
SELECT * FROM test_items WHERE name IN ('りんご', NULL);
-- 結果: id=1(りんご)のみ
-- id=2(NULL)は「NULLはNULLと等しくない」ため含まれない
-- NULLの行を取得するにはIS NULLを使う
SELECT * FROM test_items WHERE name IS NULL;
-- 結果: id=2
-- NULLを含む行も一緒に取得したい場合
SELECT * FROM test_items
WHERE name IN ('りんご', 'みかん') OR name IS NULL;
-- 結果: id=1, id=2, id=3
NOT IN句のリスト(または参照するサブクエリの結果)にNULLが1つでも含まれると、WHERE条件が全行でUNKNOWNとなり、結果が常に0件になります。「さっきまで動いていたNOT INクエリが突然0件になった」という場合、参照先テーブルにNULLが追加されたことが原因であることが多いです。本番環境でのNOT INは特に注意が必要です。
07 PERFORMANCE IN句のパフォーマンスと最適化 大量リスト・インデックス・サブクエリの実行計画を考慮する
-- 1. インデックスを活用するIN句
-- IN句の対象カラムにインデックスがあれば効率的に検索できる
-- CREATE INDEX idx_status ON orders(status_id);
EXPLAIN SELECT *
FROM orders
WHERE status_id IN (1, 2, 3);
-- type=range でインデックスを使った範囲スキャン
-- 2. IN句のリストが大量の場合はJOINを検討
-- NG(リストが1000件以上になる場合)
SELECT * FROM orders
WHERE product_id IN (1, 2, 3, 4, ... 1000件 ...);
-- OK: 一時テーブルやJOINを使う
-- (対象IDをテーブルに入れておく)
SELECT o.*
FROM orders o
INNER JOIN target_products tp ON o.product_id = tp.product_id;
-- 3. サブクエリのIN句は相関させない(非相関サブクエリにする)
-- NG: 外側のクエリの行ごとにサブクエリが実行される
-- (実際にはオプティマイザが最適化するが、意図的に相関させない)
SELECT *
FROM customers c
WHERE c.customer_id IN (
SELECT customer_id FROM orders
WHERE orders.customer_id = c.customer_id -- 相関(避けられるなら)
);
-- OK: 非相関サブクエリ(先にリストを作ってフィルタ)
SELECT *
FROM customers c
WHERE c.customer_id IN (
SELECT DISTINCT customer_id FROM orders -- 相関なし
);
📚 用語解説
実行計画(EXPLAIN):SQLクエリがどのように実行されるかの計画を表示するコマンド。MySQL/PostgreSQL/SQL Serverで使用可能。EXPLAINの出力でtypeがALLはテーブルフルスキャン(遅い)、refやrangeはインデックスを使用(速い)を意味する。IN句やWHERE句の効率化のためEXPLAINで実行計画を確認し、遅い場合はインデックスを追加するのが基本的なアプローチ。
IN句でフィルタ条件
typeがALLなら要改善
rangeならOK
IN句の対象カラムに
インデックス作成
1000件超はテーブルJOINが
パフォーマンス良いことも
08 AI_ASSIST Claude CodeでSQL IN句クエリを自動生成する テーブル構造と取得条件を伝えるだけで正確なSQLが即生成
Claude Codeを使えば、テーブルの構造と「何を取得したいか」を日本語で説明するだけで、適切なIN句やNOT EXISTS・JOINを使ったSQLクエリが即座に生成されます。EXPLAINの結果を貼って「このクエリを最適化して」という依頼にも対応できます。
| ユースケース | Claude Codeへの指示例 |
|---|---|
| 基本的なIN句 | 「ordersテーブルのstatus_idが1,2,5の注文を取得するSQLを書いて。status_id, customer_name, total_amountを取得。」 |
| サブクエリIN | 「customersテーブルから、ordersテーブルに1件以上注文がある顧客の一覧を取得するSQL。IN句とサブクエリを使って。」 |
| NOT INのNULL対策 | 「discontinued_productsにないproduct_idの注文を取得したい。NULLが含まれる可能性があるためNOT EXISTSで書いて。」 |
| EXPLAIN最適化 | 「以下のSQLのEXPLAINの出力がtype=ALLです。IN句の対象カラムにインデックスを追加する方法と、JOINで書き直すバリエーションを教えて。[SQLとEXPLAINを貼り付け]」 |
📚 用語解説
相関サブクエリ:外側のSELECT文(主クエリ)の行を参照するサブクエリ。外側の行が変わるたびにサブクエリが再実行される。EXISTSと組み合わせる場合に多い。例: WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id) のようにc.customer_idで外側の行を参照するのが相関サブクエリ。非相関サブクエリは外側の行を参照せず先に1回だけ実行されてリストを生成する。IN (SELECT ...)は通常非相関サブクエリとして扱われる。
09 CONCLUSION まとめ:SQL IN句の重要ポイント 基本から落とし穴・最適化まで
SQLをAIで効率化・学習したい方へ
IN句に限らず、JOINの最適化・ウィンドウ関数・複雑なサブクエリはClaude Codeとの組み合わせで大幅に効率化できます。
AI鬼管理では、データ活用・SQL業務のAI活用設計・実装支援を行っています。
NEXT STEP
この記事の内容を、あなたのビジネスで
実践してみませんか?
AI活用を自社で回せるようになりたい方へ
AI鬼管理
Claude Code・Cowork導入支援から業務設計・社内浸透まで実践ベースで伴走。「自社で回せる組織」を90日で作る経営者向けトレーニング。
よくある質問
Q. SQLのIN句とは何ですか?どんな時に使いますか?
A. IN句はWHERE条件の中で「このカラムの値が、指定したリストのいずれかと一致する行」を取得するための演算子です。例えばWHERE prefecture IN ('東京都', '大阪府', '神奈川県')と書くと、prefectureが東京都・大阪府・神奈川県のいずれかの行を取得します。ORを複数並べるより簡潔で読みやすく、値を追加・削除しやすいため、複数の値でフィルタリングする場面に適しています。
Q. SQLのIN句とOR演算子は何が違いますか?
A. 結果は同じですが可読性と保守性が異なります。WHERE status_id IN (1, 2, 5) はWHERE status_id = 1 OR status_id = 2 OR status_id = 5と完全に等価です。IN句のほうが値が増えても一行にまとまって読みやすく、値の追加・削除も括弧内を変更するだけで済みます。現代のRDBMSではパフォーマンスもほぼ同等です。3つ以上の値でフィルタリングする場合はIN句を使うのが一般的なベストプラクティスです。
Q. NOT INを使ったら結果が0件になりました。なぜですか?
A. NOT INのリスト(またはサブクエリの結果)にNULLが含まれているのが原因です。SQLは3値論理(TRUE/FALSE/UNKNOWN)を使い、NULLとの比較は常にUNKNOWN(真でも偽でもない)を返します。NOT IN (1, 2, NULL) は内部的にcol != 1 AND col != 2 AND col != NULLと評価されますが、col != NULLがUNKNOWNのため全体がUNKNOWNになり0件が返ります。対策はNOT EXISTSを使うか、サブクエリにWHERE col IS NOT NULLを追加してNULLを除外することです。
Q. IN句のサブクエリとEXISTSではどちらが速いですか?
A. MySQL 5.6以降、PostgreSQL、SQL Serverなどの現代のRDBMSではクエリオプティマイザが自動最適化するため、ほぼ同等のパフォーマンスになることが多いです。ただしNULLの安全性という観点では、NOT INはNULLに脆弱(全件ゼロになる)なためNOT EXISTSを推奨します。EXPLAINで実行計画を確認し、typeがALLのフルスキャンになっているならインデックスを追加するのが最も効果的なパフォーマンス改善です。
Q. IN句を使う際にインデックスは効きますか?
A. はい、IN句の対象カラムにインデックスがあれば効率的に検索できます。EXPLAINを実行してtype=rangeが表示されればインデックスを使った範囲スキャンが行われています(type=ALLはフルスキャンで遅い)。インデックスはCREATE INDEX idx_name ON テーブル名(カラム名)で作成します。ただしIN句のリストが非常に大きい(数百〜数千件以上)場合は、対象IDを一時テーブルに格納してJOINするほうが効率的なケースもあります。
Q. Claude CodeでSQL IN句を使ったクエリを作ってもらうにはどう伝えればいいですか?
A. テーブル名・カラム名(CREATE TABLE文があればそのまま貼り付けると確実)と「何を取得したいか」を日本語で伝えるのが効果的です。例: 「ordersテーブル(order_id, customer_id, status_id, total_amount)から、status_idが1か2のorder_id, total_amountを取得するSQL。total_amountの高い順にソート。」と伝えると正確なSQLが返ってきます。NOT INのNULL問題が心配な場合は「NOT INは使わずNOT EXISTSで書いて」と明示すると安全なSQLが生成されます。
Claude Codeで業務自動化を90日で叩き込む
経営者向けの伴走型パーソナルトレーニング
Claude Code を業務に落とし込む
専門研修コース一覧
受講者本人の業務を題材に、「使いこなせる」状態になるまで伴走する研修プログラム。1対1特化型・ハンズオン・法人講座の3コースを展開中。業務特化・実装まで踏み込むタイプのClaude Code研修です。
研修コース一覧を見る →AI鬼管理へのお問い合わせ
この記事を読んで気になった方へ。
AI鬼管理の専門スタッフが、御社に最適な
業務自動化プランを無料でご提案します。


