SQLでデータを抽出する際、「東京・大阪・名古屋に住む顧客を取得したい」「商品IDが1, 5, 10, 20のいずれかの注文を探したい」など、WHERE句で複数の値を条件に指定したい場面は非常によくあります。
このような場合、SQLにはIN句・OR演算子・BETWEEN・EXISTSなど、複数の方法が用意されています。しかし、方法ごとにパフォーマンスやNULL の扱いが異なるため、正しく使い分けることが重要です。
この記事では、基本的なIN句の使い方から、NOT IN のNULL問題、サブクエリとの組み合わせ、EXISTS との比較、さらにRDBMS別の制限事項まで、実務で必要な知識を体系的に解説します。
この記事で学べること
- IN句で複数の値を条件に指定する基本構文
- OR演算子との違いとパフォーマンス比較
- NOT IN 使用時のNULL問題と安全な回避方法
- サブクエリ(副問合せ)と IN の組み合わせ
- EXISTS と IN の使い分け基準
- BETWEEN で範囲指定する方法
- LIKE で複数パターンを検索する方法
- 実務で使える複合条件パターン
- RDBMS別の制限事項とパフォーマンスチューニング
サンプルデータの準備
この記事では、以下の3つのテーブルを使って解説します。実際にSQL を実行しながら読み進められるよう、CREATE TABLE と INSERT文を用意しました。
CREATE TABLE + INSERT文(クリックで展開)
-- 社員テーブル
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(50),
dept_id INT,
city VARCHAR(20),
salary INT,
hire_date DATE
);
INSERT INTO employees VALUES
(1, '田中太郎', 10, '東京', 450000, '2020-04-01'),
(2, '鈴木花子', 20, '大阪', 380000, '2019-10-15'),
(3, '佐藤一郎', 10, '東京', 520000, '2018-04-01'),
(4, '高橋美咲', 30, '名古屋', 350000, '2021-04-01'),
(5, '伊藤健太', 20, '大阪', 400000, '2020-07-01'),
(6, '渡辺直美', 10, '東京', 480000, '2019-04-01'),
(7, '山本翔太', 40, '福岡', 320000, '2022-04-01'),
(8, '中村裕子', 30, '名古屋', 370000, '2020-10-01'),
(9, '小林勇気', 20, '大阪', 410000, '2018-10-01'),
(10, '加藤明日香', NULL, '札幌', 300000, '2023-04-01');
-- 部署テーブル
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(30)
);
INSERT INTO departments VALUES
(10, '営業部'),
(20, '開発部'),
(30, '人事部'),
(40, '経理部'),
(50, '企画部');
-- 注文テーブル
CREATE TABLE orders (
order_id INT PRIMARY KEY,
emp_id INT,
product VARCHAR(30),
amount INT,
order_date DATE
);
INSERT INTO orders VALUES
(101, 1, 'ノートPC', 150000, '2024-01-15'),
(102, 2, 'モニター', 45000, '2024-02-20'),
(103, 1, 'キーボード', 12000, '2024-03-10'),
(104, 5, 'マウス', 3000, '2024-03-15'),
(105, 3, 'デスク', 80000, '2024-04-01'),
(106, 7, 'チェア', 55000, '2024-04-10'),
(107, 2, 'ヘッドセット', 8000, '2024-05-05'),
(108, 9, 'USBハブ', 4000, '2024-05-20');
employees テーブル(社員)
| emp_id |
emp_name |
dept_id |
city |
salary |
hire_date |
| 1 |
田中太郎 |
10 |
東京 |
450,000 |
2020-04-01 |
| 2 |
鈴木花子 |
20 |
大阪 |
380,000 |
2019-10-15 |
| 3 |
佐藤一郎 |
10 |
東京 |
520,000 |
2018-04-01 |
| 4 |
高橋美咲 |
30 |
名古屋 |
350,000 |
2021-04-01 |
| 5 |
伊藤健太 |
20 |
大阪 |
400,000 |
2020-07-01 |
| 6 |
渡辺直美 |
10 |
東京 |
480,000 |
2019-04-01 |
| 7 |
山本翔太 |
40 |
福岡 |
320,000 |
2022-04-01 |
| 8 |
中村裕子 |
30 |
名古屋 |
370,000 |
2020-10-01 |
| 9 |
小林勇気 |
20 |
大阪 |
410,000 |
2018-10-01 |
| 10 |
加藤明日香 |
NULL |
札幌 |
300,000 |
2023-04-01 |
注意:emp_id=10 の加藤明日香は dept_id が NULL です。NOT IN の解説でこの NULL が重要な意味を持ちます。
IN句で複数の値を条件に指定する【基本】
IN句の基本構文
IN句は、指定したカラムの値が「リストのいずれかに一致する」行を抽出する構文です。OR演算子を複数書く代わりに、簡潔に記述できます。
IN句の基本構文
SELECT カラム名
FROM テーブル名
WHERE カラム名 IN (値1, 値2, 値3, ...);
IN の括弧内に、条件としたい値をカンマ区切りで並べます。文字列はシングルクォートで囲み、数値はそのまま記述します。
文字列カラムに IN を使う
東京・大阪・名古屋に勤務する社員を取得してみましょう。
文字列に IN を使う例
SELECT emp_id, emp_name, city
FROM employees
WHERE city IN ('東京', '大阪', '名古屋');
実行結果
emp_id | emp_name | city
-------+----------+--------
1 | 田中太郎 | 東京
2 | 鈴木花子 | 大阪
3 | 佐藤一郎 | 東京
4 | 高橋美咲 | 名古屋
5 | 伊藤健太 | 大阪
6 | 渡辺直美 | 東京
8 | 中村裕子 | 名古屋
9 | 小林勇気 | 大阪
(8 rows)
福岡の山本翔太(emp_id=7)と札幌の加藤明日香(emp_id=10)は、INリストに含まれていないため除外されます。
数値カラムに IN を使う
部署ID が 10(営業部)または 20(開発部)の社員を取得します。
数値に IN を使う例
SELECT emp_id, emp_name, dept_id
FROM employees
WHERE dept_id IN (10, 20);
実行結果
emp_id | emp_name | dept_id
-------+----------+---------
1 | 田中太郎 | 10
2 | 鈴木花子 | 20
3 | 佐藤一郎 | 10
5 | 伊藤健太 | 20
6 | 渡辺直美 | 10
9 | 小林勇気 | 20
(6 rows)
ポイント:数値の場合はクォートが不要です。文字列と数値を混在させるとエラーや暗黙の型変換が発生するので注意しましょう。
日付カラムに IN を使う
特定の入社日に該当する社員を取得する例です。
日付に IN を使う例
SELECT emp_id, emp_name, hire_date
FROM employees
WHERE hire_date IN ('2020-04-01', '2021-04-01', '2022-04-01');
実行結果
emp_id | emp_name | hire_date
-------+----------+------------
1 | 田中太郎 | 2020-04-01
4 | 高橋美咲 | 2021-04-01
7 | 山本翔太 | 2022-04-01
(3 rows)
注意:日付のフォーマットはRDBMSによって異なります。MySQL は 'YYYY-MM-DD'、Oracle は TO_DATE('2020/04/01', 'YYYY/MM/DD') や日付リテラル DATE '2020-04-01' を使います。
OR演算子で複数条件を指定する方法【IN との比較】
OR で同じことを書くと?
IN句を使わずに OR演算子で同じ結果を得ることもできます。先ほどの「東京・大阪・名古屋の社員」をOR で書くと以下のようになります。
OR演算子で複数条件を指定
SELECT emp_id, emp_name, city
FROM employees
WHERE city = '東京'
OR city = '大阪'
OR city = '名古屋';
結果は IN句のときと同じです。しかし、値が増えるとORの記述はどんどん長くなります。
IN と OR の比較
| 比較項目 |
IN句 |
OR演算子 |
| 可読性 |
値が多くても簡潔 |
値が増えると冗長になる |
| 保守性 |
値の追加・削除が容易 |
各行を修正する必要あり |
| パフォーマンス |
最適化されやすい |
DBによっては IN に変換される |
| 他条件との組合せ |
AND と混在しても安全 |
括弧の付け忘れに注意 |
| サブクエリ利用 |
IN (SELECT …) が可能 |
サブクエリは使えない |
OR の落とし穴:AND との優先順位
OR を使う場合、AND との演算子優先順位に注意が必要です。AND は OR より優先されるため、意図しない結果になることがあります。
間違いやすいパターン
-- NG: 意図しない結果になる
SELECT emp_id, emp_name, city, salary
FROM employees
WHERE city = '東京'
OR city = '大阪'
AND salary >= 400000;
-- AND が先に評価され「大阪かつ40万以上」OR「東京」になってしまう
-- OK: 括弧で明示する
SELECT emp_id, emp_name, city, salary
FROM employees
WHERE (city = '東京' OR city = '大阪')
AND salary >= 400000;
-- BEST: IN句なら括弧不要で安全
SELECT emp_id, emp_name, city, salary
FROM employees
WHERE city IN ('東京', '大阪')
AND salary >= 400000;
ポイント:IN句は内部的に1つの条件として扱われるため、AND との組み合わせで括弧の付け忘れを心配する必要がありません。条件が3つ以上ある場合は、可読性・安全性の観点からIN句を使いましょう。
NOT IN で「含まれない」データを抽出する
NOT IN の基本構文
NOT IN は IN の逆で、リストに含まれない値を持つ行を取得します。
NOT IN の基本構文
SELECT カラム名
FROM テーブル名
WHERE カラム名 NOT IN (値1, 値2, 値3, ...);
東京・大阪以外の社員を取得
SELECT emp_id, emp_name, city
FROM employees
WHERE city NOT IN ('東京', '大阪');
実行結果
emp_id | emp_name | city
-------+------------+--------
4 | 高橋美咲 | 名古屋
7 | 山本翔太 | 福岡
8 | 中村裕子 | 名古屋
10 | 加藤明日香 | 札幌
(4 rows)
NOT IN の落とし穴:NULL が含まれると結果が0件になる
NOT IN を使う際の最も危険な落とし穴は、リストまたはカラムに NULL が含まれる場合です。これは実務で非常によくあるバグの原因になります。
NOT IN + NULL の問題
-- 「営業部(10)・開発部(20)以外」の社員を取得したい
-- サブクエリで dept_id を取得
SELECT emp_id, emp_name, dept_id
FROM employees
WHERE dept_id NOT IN (
SELECT dept_id
FROM employees
WHERE dept_id IN (10, 20)
);
上記は正常に動作しますが、次のようにNULLを含むサブクエリと NOT IN を組み合わせると問題が発生します。
NULL を含むサブクエリとの NOT IN
-- employees テーブルの dept_id には NULL がある(emp_id=10)
SELECT dept_id, dept_name
FROM departments
WHERE dept_id NOT IN (
SELECT dept_id FROM employees
);
-- 結果: 0件! dept_id=50(企画部)も返らない
これは SQL の NULL の仕様によるものです。NOT IN (10, 20, 30, 40, NULL) は内部的に以下のように評価されます。
NOT IN + NULL の内部評価
-- dept_id = 50 の場合の評価過程
50 NOT IN (10, 20, 30, 40, NULL)
-- 展開すると
50 != 10 → TRUE
50 != 20 → TRUE
50 != 30 → TRUE
50 != 40 → TRUE
50 != NULL → UNKNOWN -- NULLとの比較は常にUNKNOWN
-- TRUE AND TRUE AND TRUE AND TRUE AND UNKNOWN = UNKNOWN
-- WHERE句は UNKNOWN を FALSE として扱う → 行は返されない
重要:NOT IN のリストに NULL が1つでも含まれると、すべての行が結果から除外されます。これは SQL の三値論理(TRUE / FALSE / UNKNOWN)の仕様です。IN の場合は NULL があっても他の値で一致すれば行は返されるため、IN では問題になりません。
NOT IN + NULL 問題の回避方法
この問題を回避するには、以下の3つの方法があります。
回避方法1: IS NOT NULL で除外
SELECT dept_id, dept_name
FROM departments
WHERE dept_id NOT IN (
SELECT dept_id
FROM employees
WHERE dept_id IS NOT NULL
);
回避方法2: NOT EXISTS を使う(推奨)
SELECT d.dept_id, d.dept_name
FROM departments d
WHERE NOT EXISTS (
SELECT 1
FROM employees e
WHERE e.dept_id = d.dept_id
);
回避方法3: LEFT JOIN + IS NULL
SELECT d.dept_id, d.dept_name
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
WHERE e.dept_id IS NULL;
上記3つとも同じ結果
dept_id | dept_name
--------+-----------
50 | 企画部
(1 row)
実務のベストプラクティス:「含まれない」条件にはNOT INよりもNOT EXISTSを使うのが安全です。NOT EXISTS は NULL の影響を受けず、大量データでもパフォーマンスが安定します。
サブクエリ(副問合せ)と IN の組み合わせ
サブクエリで動的にリストを生成する
IN句のリストには固定値だけでなく、SELECT文(サブクエリ)を指定できます。これにより、別のテーブルから動的に条件を生成できます。
サブクエリの基本構文
SELECT カラム名
FROM テーブルA
WHERE カラム名 IN (
SELECT カラム名
FROM テーブルB
WHERE 条件
);
実践例1: 注文がある社員を取得する
orders テーブルに注文がある社員の情報を employees テーブルから取得します。
サブクエリで注文がある社員を取得
SELECT emp_id, emp_name, dept_id
FROM employees
WHERE emp_id IN (
SELECT DISTINCT emp_id
FROM orders
);
実行結果
emp_id | emp_name | dept_id
-------+----------+---------
1 | 田中太郎 | 10
2 | 鈴木花子 | 20
3 | 佐藤一郎 | 10
5 | 伊藤健太 | 20
7 | 山本翔太 | 40
9 | 小林勇気 | 20
(6 rows)
実践例2: 条件付きサブクエリ
5万円以上の注文がある社員のみを取得します。サブクエリ内に WHERE 条件を追加します。
条件付きサブクエリ
SELECT emp_id, emp_name, city
FROM employees
WHERE emp_id IN (
SELECT emp_id
FROM orders
WHERE amount >= 50000
);
実行結果
emp_id | emp_name | city
-------+----------+------
1 | 田中太郎 | 東京
3 | 佐藤一郎 | 東京
7 | 山本翔太 | 福岡
(3 rows)
EXISTS と IN の違い・使い分け
サブクエリで「関連するデータがあるか」を判定する方法として、IN の他に EXISTS があります。どちらも似た結果を返しますが、動作の仕組みとパフォーマンス特性が異なります。
EXISTS の基本構文
EXISTS の基本構文
SELECT カラム名
FROM テーブルA a
WHERE EXISTS (
SELECT 1
FROM テーブルB b
WHERE b.カラム = a.カラム
);
IN と EXISTS の書き換え例
先ほどの「注文がある社員」をEXISTS で書き換えてみましょう。
IN → EXISTS への書き換え
-- IN版
SELECT emp_id, emp_name
FROM employees
WHERE emp_id IN (SELECT emp_id FROM orders);
-- EXISTS版
SELECT e.emp_id, e.emp_name
FROM employees e
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.emp_id = e.emp_id
);
EXISTS と IN の比較
| 比較項目 |
IN (サブクエリ) |
EXISTS |
| 動作原理 |
サブクエリを先に実行しリストを作成 |
外側の行ごとにサブクエリを評価 |
| NULLの扱い |
NOT IN + NULL で問題あり |
NULL の影響なし |
| サブクエリの結果が少量 |
IN が有利 |
差は小さい |
| サブクエリの結果が大量 |
リストが巨大になり遅い |
EXISTS が有利 |
| 相関サブクエリ |
通常は使わない |
相関サブクエリが基本 |
| インデックス活用 |
サブクエリ側に必要 |
結合カラムに必要 |
使い分けの目安:
- 固定値リスト → IN
- サブクエリの結果が少量(数百件以下) → IN
- サブクエリの結果が大量 → EXISTS
- NOT IN を使いたい場面 → NOT EXISTS(NULL安全)
BETWEEN で範囲を指定する
BETWEEN の基本構文
連続する値の範囲(数値・日付など)を条件にしたい場合は、BETWEEN を使います。
BETWEEN の基本構文
SELECT カラム名
FROM テーブル名
WHERE カラム名 BETWEEN 開始値 AND 終了値;
-- 上記は以下と同じ意味
WHERE カラム名 >= 開始値 AND カラム名 <= 終了値;
注意:BETWEEN は開始値と終了値を含みます(以上・以下)。「より大きい」「未満」が必要な場合は > と < を使ってください。
数値の範囲指定
給与が350,000円以上450,000円以下の社員を取得します。
数値のBETWEEN
SELECT emp_id, emp_name, salary
FROM employees
WHERE salary BETWEEN 350000 AND 450000;
実行結果
emp_id | emp_name | salary
-------+----------+---------
1 | 田中太郎 | 450,000
2 | 鈴木花子 | 380,000
4 | 高橋美咲 | 350,000
5 | 伊藤健太 | 400,000
8 | 中村裕子 | 370,000
9 | 小林勇気 | 410,000
(6 rows)
日付の範囲指定
2020年に入社した社員を取得します。
日付のBETWEEN
SELECT emp_id, emp_name, hire_date
FROM employees
WHERE hire_date BETWEEN '2020-01-01' AND '2020-12-31';
実行結果
emp_id | emp_name | hire_date
-------+----------+------------
1 | 田中太郎 | 2020-04-01
5 | 伊藤健太 | 2020-07-01
8 | 中村裕子 | 2020-10-01
(3 rows)
NOT BETWEEN で範囲外を指定する
NOT BETWEEN の例
-- 給与が350,000〜450,000の範囲外の社員
SELECT emp_id, emp_name, salary
FROM employees
WHERE salary NOT BETWEEN 350000 AND 450000;
実行結果
emp_id | emp_name | salary
-------+------------+---------
3 | 佐藤一郎 | 520,000
6 | 渡辺直美 | 480,000
7 | 山本翔太 | 320,000
10 | 加藤明日香 | 300,000
(4 rows)
IN と BETWEEN の使い分け
| 条件の種類 |
適切な方法 |
例 |
| 離散的な値 |
IN |
都市名、ステータスコード |
| 連続する範囲 |
BETWEEN |
金額範囲、日付範囲 |
| 不連続な範囲 |
IN または OR |
部署ID 10, 30, 50 |
| 動的なリスト |
IN (サブクエリ) |
別テーブルの値 |
LIKE で複数のパターンを検索する
文字列の部分一致で複数のパターンを条件にしたい場合、LIKE と OR を組み合わせます。IN句は完全一致のみ対応しているため、部分一致には使えません。
OR で複数の LIKE を指定
複数のLIKEパターン
-- 名前に「田」または「藤」が含まれる社員
SELECT emp_id, emp_name
FROM employees
WHERE emp_name LIKE '%田%'
OR emp_name LIKE '%藤%';
実行結果
emp_id | emp_name
-------+----------
1 | 田中太郎
3 | 佐藤一郎
5 | 伊藤健太
10 | 加藤明日香
(4 rows)
RDBMS別:正規表現で複数パターンをまとめる
パターンが多い場合、正規表現を使うと1つの条件にまとめられます。ただし、構文はRDBMSごとに異なります。
RDBMS別の正規表現
-- MySQL
SELECT emp_id, emp_name
FROM employees
WHERE emp_name REGEXP '田|藤';
-- PostgreSQL
SELECT emp_id, emp_name
FROM employees
WHERE emp_name ~ '田|藤';
-- Oracle
SELECT emp_id, emp_name
FROM employees
WHERE REGEXP_LIKE(emp_name, '田|藤');
-- SQL Server
-- 標準の正規表現は非対応。LIKE + OR を使用
実務で使える複合条件パターン
実際の業務では、IN・BETWEEN・LIKE を組み合わせて使うことがほとんどです。ここでは実務でよく使うパターンを紹介します。
パターン1: IN + AND(カテゴリ絞り込み + 金額フィルタ)
IN + AND の組み合わせ
-- 営業部・開発部で、給与40万以上の社員
SELECT e.emp_id, e.emp_name, d.dept_name, e.salary
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.dept_id IN (10, 20)
AND e.salary >= 400000;
実行結果
emp_id | emp_name | dept_name | salary
-------+----------+-----------+---------
1 | 田中太郎 | 営業部 | 450,000
3 | 佐藤一郎 | 営業部 | 520,000
5 | 伊藤健太 | 開発部 | 400,000
6 | 渡辺直美 | 営業部 | 480,000
9 | 小林勇気 | 開発部 | 410,000
(5 rows)
パターン2: IN + BETWEEN(都市 + 期間)
IN + BETWEEN の組み合わせ
-- 東京・大阪の社員で、2019〜2020年に入社した人
SELECT emp_id, emp_name, city, hire_date
FROM employees
WHERE city IN ('東京', '大阪')
AND hire_date BETWEEN '2019-01-01' AND '2020-12-31';
実行結果
emp_id | emp_name | city | hire_date
-------+----------+------+------------
1 | 田中太郎 | 東京 | 2020-04-01
2 | 鈴木花子 | 大阪 | 2019-10-15
5 | 伊藤健太 | 大阪 | 2020-07-01
6 | 渡辺直美 | 東京 | 2019-04-01
(4 rows)
パターン3: 複数のINを組み合わせる
複数の IN を AND で結合
-- 営業部・開発部の、東京・大阪勤務の社員
SELECT emp_id, emp_name, dept_id, city
FROM employees
WHERE dept_id IN (10, 20)
AND city IN ('東京', '大阪');
実行結果
emp_id | emp_name | dept_id | city
-------+----------+---------+------
1 | 田中太郎 | 10 | 東京
2 | 鈴木花子 | 20 | 大阪
3 | 佐藤一郎 | 10 | 東京
5 | 伊藤健太 | 20 | 大阪
6 | 渡辺直美 | 10 | 東京
9 | 小林勇気 | 20 | 大阪
(6 rows)
パターン4: CASE式と IN の組み合わせ
IN は WHERE句だけでなく、CASE式の中でも使えます。分類やラベル付けに便利です。
CASE式でINを使った分類
SELECT
emp_id,
emp_name,
city,
CASE
WHEN city IN ('東京', '大阪', '名古屋') THEN '三大都市圏'
WHEN city IN ('福岡', '札幌') THEN '地方主要都市'
ELSE 'その他'
END AS area_type
FROM employees;
実行結果
emp_id | emp_name | city | area_type
-------+------------+--------+------------
1 | 田中太郎 | 東京 | 三大都市圏
2 | 鈴木花子 | 大阪 | 三大都市圏
3 | 佐藤一郎 | 東京 | 三大都市圏
4 | 高橋美咲 | 名古屋 | 三大都市圏
5 | 伊藤健太 | 大阪 | 三大都市圏
6 | 渡辺直美 | 東京 | 三大都市圏
7 | 山本翔太 | 福岡 | 地方主要都市
8 | 中村裕子 | 名古屋 | 三大都市圏
9 | 小林勇気 | 大阪 | 三大都市圏
10 | 加藤明日香 | 札幌 | 地方主要都市
(10 rows)
UPDATE・DELETE での IN句の活用
IN句は SELECT だけでなく、UPDATE や DELETE でも活用できます。複数レコードを一括で更新・削除する場面で非常に便利です。
UPDATE + IN
UPDATE + IN で一括更新
-- 営業部・開発部の社員に5%昇給
UPDATE employees
SET salary = salary * 1.05
WHERE dept_id IN (10, 20);
DELETE + IN
DELETE + IN で一括削除
-- 特定の注文を削除
DELETE FROM orders
WHERE order_id IN (105, 106, 107);
注意:UPDATE や DELETE で IN を使う場合は、必ず先に SELECT で対象レコードを確認してから実行しましょう。WHERE句の書き間違いで大量のデータを破壊するリスクがあります。
DELETE + IN + サブクエリの実践例
退職者の注文データを削除
-- 部署未配属(退職予定)の社員の注文を削除
DELETE FROM orders
WHERE emp_id IN (
SELECT emp_id
FROM employees
WHERE dept_id IS NULL
);
パフォーマンス最適化とインデックス
IN句のパフォーマンスは、値の数、インデックスの有無、RDBMSの種類によって大きく変わります。ここでは実務で知っておくべきパフォーマンス知識をまとめます。
IN句とインデックスの関係
IN句の対象カラムにインデックスがある場合、多くのRDBMSはインデックスを活用してくれます。ただし、値の数が増えすぎるとフルスキャンに切り替わることがあります。
インデックスの作成例
-- IN句で頻繁に検索するカラムにインデックスを作成
CREATE INDEX idx_employees_city ON employees(city);
CREATE INDEX idx_employees_dept ON employees(dept_id);
-- 実行計画を確認(MySQL)
EXPLAIN SELECT * FROM employees
WHERE city IN ('東京', '大阪');
RDBMS別のIN句の制限
| RDBMS |
IN句の値数上限 |
対処法 |
| Oracle |
1,000個 |
複数のIN句をORで結合 or 一時テーブル |
| SQL Server |
明示的制限なし(パラメータ上限2,100) |
テーブル値パラメータ |
| MySQL |
明示的制限なし(max_allowed_packetに依存) |
一時テーブルとJOIN |
| PostgreSQL |
明示的制限なし |
= ANY(ARRAY[…]) も利用可 |
Oracle の IN句 1,000件制限の対処法
Oracle では IN句に1,000個以上の値を指定するとエラー(ORA-01795)が発生します。以下の方法で回避できます。
Oracle 1,000件制限の回避(OR分割)
-- 方法1: 複数の IN を OR で結合
SELECT *
FROM large_table
WHERE id IN (値1〜値1000)
OR id IN (値1001〜値2000);
-- 方法2: 一時テーブルに格納してJOIN(推奨)
CREATE GLOBAL TEMPORARY TABLE tmp_ids (
id NUMBER
) ON COMMIT DELETE ROWS;
INSERT INTO tmp_ids VALUES (1);
INSERT INTO tmp_ids VALUES (2);
-- ... 大量のINSERT ...
SELECT t.*
FROM large_table t
JOIN tmp_ids i ON t.id = i.id;
パフォーマンス改善のベストプラクティス
パフォーマンスのポイント
- インデックスを作成する: IN句で検索するカラムにはインデックスを付ける
- 値の数を抑える: 100個以上はサブクエリや一時テーブルの使用を検討
- EXPLAIN で確認する: 実行計画でインデックスが使われているか確認
- 大量データは EXISTS に置き換え: サブクエリの結果が数万件以上ならEXISTS推奨
- データ型を一致させる: 暗黙の型変換が発生するとインデックスが効かない
よくあるエラーとトラブルシューティング
| 症状 |
原因 |
解決策 |
| NOT IN で結果が0件 |
リストにNULLが含まれている |
IS NOT NULL で除外 or NOT EXISTS に変更 |
| ORA-01795エラー |
IN句の値が1,000個超(Oracle) |
OR分割 or 一時テーブル |
| 型変換エラー |
文字列カラムに数値を指定 |
データ型を揃える |
| IN句が遅い |
インデックスなし or 値が多すぎる |
インデックス作成 or EXISTS に変更 |
| OR + AND で意図しない結果 |
演算子の優先順位ミス |
IN句に書き換え or 括弧を追加 |
| 大文字・小文字で一致しない |
照合順序(Collation)が CS |
UPPER() / LOWER() で統一 |
大文字・小文字の問題と対処
文字列の IN句で大文字・小文字が一致しない場合の対処法です。
大文字・小文字を無視した検索
-- UPPER() で統一して比較
SELECT *
FROM products
WHERE UPPER(status) IN ('ACTIVE', 'PENDING');
-- MySQL は照合順序が ci(case-insensitive)ならそのまま比較可能
-- PostgreSQL は ILIKE や citext 型を使う方法もある
注意:カラムに関数(UPPER等)を適用すると、通常のインデックスが使われなくなります。頻繁に検索する場合は関数インデックス(ファンクションベースインデックス)の作成を検討してください。
RDBMS別の構文と注意点
IN句の基本構文はどのRDBMSでも同じですが、拡張構文や細かい挙動に違いがあります。
MySQL固有の機能
MySQL: FIND_IN_SET(カンマ区切り文字列の検索)
-- カンマ区切りの文字列から検索(非推奨だが既存システムで見かける)
SELECT *
FROM articles
WHERE FIND_IN_SET('SQL', tags) > 0;
PostgreSQL固有の機能
PostgreSQL: ANY / ALL 演算子
-- IN の代わりに ANY を使用
SELECT * FROM employees
WHERE city = ANY(ARRAY['東京', '大阪']);
-- NOT IN の代わりに ALL を使用
SELECT * FROM employees
WHERE city != ALL(ARRAY['東京', '大阪']);
Oracle固有の注意点
Oracle: 日付のIN句
-- Oracle は暗黙の日付変換が環境依存のため、明示的に変換する
SELECT * FROM employees
WHERE hire_date IN (
DATE '2020-04-01',
DATE '2021-04-01'
);
-- または TO_DATE を使用
SELECT * FROM employees
WHERE hire_date IN (
TO_DATE('2020/04/01', 'YYYY/MM/DD'),
TO_DATE('2021/04/01', 'YYYY/MM/DD')
);
まとめ
この記事では、SQLのWHERE句で複数の値を条件に指定する方法を網羅的に解説しました。最後に要点を整理します。
| 方法 |
用途 |
推奨場面 |
| IN (値リスト) |
離散的な複数値の完全一致 |
最も基本的で推奨される方法 |
| OR |
異なるカラムの条件を結合 |
同一カラムなら IN を優先 |
| NOT IN |
特定値の除外 |
固定値のみ。サブクエリならNOT EXISTS推奨 |
| IN (サブクエリ) |
動的なリスト生成 |
サブクエリ結果が少量の場合 |
| EXISTS |
相関サブクエリでの存在確認 |
大量データ・NULL安全が必要な場合 |
| BETWEEN |
連続する範囲の指定 |
数値・日付の範囲検索 |
| LIKE + OR |
複数パターンの部分一致 |
正規表現が使えない場合 |
実務での選択基準
- まずIN句を検討: 複数値の条件指定では、IN句が最もシンプルで可読性が高い
- NOT IN は避ける: NULLの問題があるため、NOT EXISTS または LEFT JOIN + IS NULL を使う
- 大量データはEXISTS: サブクエリの結果が数千件以上になる場合はEXISTSが安全
- 範囲はBETWEEN: 連続する値の範囲はBETWEENで簡潔に書ける
- インデックスを忘れずに: IN句で検索するカラムにはインデックスを設定する
複数条件の指定は SQL の基本でありながら、NOT IN の NULL 問題やパフォーマンスの落とし穴など、実務では知識の深さが問われるテーマです。この記事で解説した内容を押さえておけば、日常のクエリ作成からパフォーマンスチューニングまで、自信を持って対応できるはずです。