【SQL】抽出条件に複数の値を設定する方法|IN句・OR・BETWEEN・EXISTS完全ガイド

SQLでデータを抽出する際、「東京・大阪・名古屋に住む顧客を取得したい」「商品IDが1, 5, 10, 20のいずれかの注文を探したい」など、WHERE句で複数の値を条件に指定したい場面は非常によくあります。

このような場合、SQLにはIN句OR演算子BETWEENEXISTSなど、複数の方法が用意されています。しかし、方法ごとにパフォーマンスNULL の扱いが異なるため、正しく使い分けることが重要です。

この記事では、基本的なIN句の使い方から、NOT IN のNULL問題、サブクエリとの組み合わせ、EXISTS との比較、さらにRDBMS別の制限事項まで、実務で必要な知識を体系的に解説します。

この記事で学べること

  • IN句で複数の値を条件に指定する基本構文
  • OR演算子との違いとパフォーマンス比較
  • NOT IN 使用時のNULL問題と安全な回避方法
  • サブクエリ(副問合せ)と IN の組み合わせ
  • EXISTS と IN の使い分け基準
  • BETWEEN で範囲指定する方法
  • LIKE で複数パターンを検索する方法
  • 実務で使える複合条件パターン
  • RDBMS別の制限事項とパフォーマンスチューニング
スポンサーリンク
  1. サンプルデータの準備
    1. employees テーブル(社員)
  2. IN句で複数の値を条件に指定する【基本】
    1. IN句の基本構文
    2. 文字列カラムに IN を使う
    3. 数値カラムに IN を使う
    4. 日付カラムに IN を使う
  3. OR演算子で複数条件を指定する方法【IN との比較】
    1. OR で同じことを書くと?
    2. IN と OR の比較
    3. OR の落とし穴:AND との優先順位
  4. NOT IN で「含まれない」データを抽出する
    1. NOT IN の基本構文
    2. NOT IN の落とし穴:NULL が含まれると結果が0件になる
    3. NOT IN + NULL 問題の回避方法
  5. サブクエリ(副問合せ)と IN の組み合わせ
    1. サブクエリで動的にリストを生成する
    2. 実践例1: 注文がある社員を取得する
    3. 実践例2: 条件付きサブクエリ
  6. EXISTS と IN の違い・使い分け
    1. EXISTS の基本構文
    2. IN と EXISTS の書き換え例
    3. EXISTS と IN の比較
  7. BETWEEN で範囲を指定する
    1. BETWEEN の基本構文
    2. 数値の範囲指定
    3. 日付の範囲指定
    4. NOT BETWEEN で範囲外を指定する
    5. IN と BETWEEN の使い分け
  8. LIKE で複数のパターンを検索する
    1. OR で複数の LIKE を指定
    2. RDBMS別:正規表現で複数パターンをまとめる
  9. 実務で使える複合条件パターン
    1. パターン1: IN + AND(カテゴリ絞り込み + 金額フィルタ)
    2. パターン2: IN + BETWEEN(都市 + 期間)
    3. パターン3: 複数のINを組み合わせる
    4. パターン4: CASE式と IN の組み合わせ
  10. UPDATE・DELETE での IN句の活用
    1. UPDATE + IN
    2. DELETE + IN
    3. DELETE + IN + サブクエリの実践例
  11. パフォーマンス最適化とインデックス
    1. IN句とインデックスの関係
    2. RDBMS別のIN句の制限
    3. Oracle の IN句 1,000件制限の対処法
    4. パフォーマンス改善のベストプラクティス
  12. よくあるエラーとトラブルシューティング
    1. 大文字・小文字の問題と対処
  13. RDBMS別の構文と注意点
    1. MySQL固有の機能
    2. PostgreSQL固有の機能
    3. Oracle固有の注意点
  14. まとめ

サンプルデータの準備

この記事では、以下の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 != NULLUNKNOWN  -- 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 だけでなく、UPDATEDELETE でも活用できます。複数レコードを一括で更新・削除する場面で非常に便利です。

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 問題やパフォーマンスの落とし穴など、実務では知識の深さが問われるテーマです。この記事で解説した内容を押さえておけば、日常のクエリ作成からパフォーマンスチューニングまで、自信を持って対応できるはずです。