【SQL】WHERE句で複数条件を組み合わせる完全ガイド|AND・OR・NOT・優先順位・SARGable・HAVING vs WHERE まで解説

【SQL】WHERE句で複数条件を組み合わせる完全ガイド|AND・OR・NOT・優先順位・SARGable・HAVING vs WHERE まで解説 SQL

SQL でデータを絞り込む WHERE 句は、AND・OR・NOT を組み合わせることで複雑な条件を表現できます。しかし「AND と OR が混在すると期待通りに絞り込めない」「NOT IN に NULL が含まれると全件ゼロになる」など、知らないと踏んでしまう落とし穴が存在します。

この記事では複数条件の組み合わせに必要な論理・優先順位・パフォーマンスのポイントを体系的に解説します。各演算子(IN・BETWEEN・LIKE・EXISTS)の詳細は専門記事へ誘導します。

サンプルデータ(以降の例で使用)
-- orders テーブル(注文)
-- id | customer | status   | amount | category | order_date
--  1 | 田中     | shipped  |  12000 | 家電     | 2024-01-15
--  2 | 鈴木     | pending  |   3500 | 食品     | 2024-02-01
--  3 | 高橋     | canceled |  85000 | 家電     | 2024-02-20
--  4 | 田中     | shipped  |   2800 | 食品     | 2024-03-05
--  5 | 伊藤     | pending  |  45000 | 家電     | 2024-03-10
--  6 | 渡辺     | shipped  |   9500 | 衣類     | 2024-04-01
--  7 | 田中     | pending  |    600 | 食品     | 2024-04-15
--  8 | 高橋     | shipped  |  NULL  | 衣類     | 2024-04-20
スポンサーリンク

WHERE 句の役割と書き方の基本

WHERE 句は FROM・JOIN の後に処理され、個々の行を条件で絞り込みます。複数の条件は ANDORNOT で組み合わせます。

WHERE 句の基本形
-- 単一条件
SELECT * FROM orders WHERE status = 'shipped';

-- 複数条件(AND: 両方を満たす行のみ)
SELECT * FROM orders
WHERE status = 'shipped'
  AND amount >= 10000;

-- 複数条件(OR: どちらかを満たす行)
SELECT * FROM orders
WHERE category = '家電'
   OR category = '食品';

-- 否定(NOT: 条件を満たさない行)
SELECT * FROM orders
WHERE NOT status = 'canceled';
-- 同じ意味: WHERE status <> 'canceled'
SQL の実行順序:
WHERE は FROM/JOIN(①)→ WHERE(②)→ GROUP BY(③)→ HAVING(④)→ SELECT(⑤) の順で処理されます。そのため WHERE 句では SELECT で定義したエイリアスは使えません。実行順序の詳細はSELECT 文完全ガイドを参照してください。

AND・OR・NOT の論理と NULL(三値論理)

SQL では TRUE・FALSE の 2 値ではなく、TRUE・FALSE・UNKNOWN の三値論理を使います。WHERE 条件が UNKNOWN の行は結果から除外されます。NULL との比較が絡むと予期しない動作の原因になります。

A B A AND B A OR B NOT A
TRUE TRUE TRUE TRUE FALSE
TRUE FALSE FALSE TRUE FALSE
TRUE UNKNOWN UNKNOWN TRUE FALSE
FALSE FALSE FALSE FALSE TRUE
FALSE UNKNOWN FALSE UNKNOWN TRUE
UNKNOWN UNKNOWN UNKNOWN UNKNOWN UNKNOWN
NULL との比較は常に UNKNOWN:
amount = NULLamount <> NULL も結果は UNKNOWN(≠ FALSE)です。UNKNOWN の行は WHERE で除外されるため 0 件になります。NULL の検索には必ず IS NULL / IS NOT NULL を使ってください。NULL を含む列の絞り込みパターンはNULL を含む列の絞り込み完全ガイドで詳しく解説しています。

AND と OR の優先順位と括弧による制御

AND は OR より優先度が高いため、混在した条件は意図と異なる結果になりやすいです。括弧 () で明示的にグループ化する習慣をつけてください。

優先順位の違いで結果が変わる例
-- 「家電 または(食品 かつ amount >= 5000)」と解釈される
-- → 家電は全件(amount不問)、食品は5000以上のみ
SELECT * FROM orders
WHERE category = '家電'
   OR category = '食品' AND amount >= 5000;

-- 等価な書き方(明示的に AND を先にまとめる)
SELECT * FROM orders
WHERE category = '家電'
   OR (category = '食品' AND amount >= 5000);

-- 意図が「(家電 または 食品)かつ amount >= 5000」なら括弧が必要
SELECT * FROM orders
WHERE (category = '家電' OR category = '食品')
  AND amount >= 5000;
演算子 優先度 補足
NOT 高(1位) 単一条件の否定。AND・OR より先に評価される
AND 中(2位) 両条件が TRUE の行のみ
OR 低(3位) どちらかが TRUE の行
() 最高 括弧内を先に評価。優先順位を明示したいときは常に使う
複数条件を書くときの鉄則:

  • AND と OR が混在するときは、必ず括弧でグループ化する
  • 「A または B、かつ C」の場合: (A OR B) AND C と括弧を明示する
  • 条件が多くなるほど意図が分かりにくくなるため、コメント(-- ここは配送済みかつ高額)を添えるとよい

WHERE で使える絞り込み演算子の一覧

演算子 用途 詳細
= / <> / != 等値・不等値の比較 status = 'shipped'
> / < / >= / <= 大小比較 amount >= 10000
BETWEEN A AND B 範囲(A 以上 B 以下) amount BETWEEN 1000 AND 50000 IN/BETWEEN ガイド
IN (値1, 値2, ...) リストのいずれかと一致 category IN ('家電', '衣類') IN/BETWEEN ガイド
LIKE 'パターン' 文字列パターン一致(% と _) customer LIKE '田%'
IS NULL / IS NOT NULL NULL かどうか amount IS NOT NULL NULL フィルタガイド
NOT IN / NOT LIKE 否定条件 status NOT IN ('canceled') NOT 条件ガイド
EXISTS (サブクエリ) サブクエリの結果が存在するか EXISTS (SELECT 1 FROM ...) IN/BETWEEN ガイド

実務でよく使う複数条件パターン集

ステータス + 金額の組み合わせ
-- shipped かつ 1万円以上の注文
SELECT id, customer, amount, order_date
FROM orders
WHERE status = 'shipped'
  AND amount >= 10000
ORDER BY amount DESC;

-- 結果: id=1(田中, 12000円)のみ

-- キャンセル以外かつ 1 万円未満
SELECT id, customer, amount, status
FROM orders
WHERE status <> 'canceled'
  AND amount < 10000;
複数ステータス + 期間の組み合わせ
-- 2024年2月〜3月の家電・衣類注文
SELECT id, customer, category, amount, order_date
FROM orders
WHERE category IN ('家電', '衣類')
  AND order_date BETWEEN '2024-02-01' AND '2024-03-31'
ORDER BY order_date;

-- 「出荷済み または キャンセル」で 2024年1月以降
SELECT id, customer, status, order_date
FROM orders
WHERE (status = 'shipped' OR status = 'canceled')
  AND order_date >= '2024-01-01'
ORDER BY order_date;
NULL を含む複合条件
-- amount が NULL または 1000 円未満の注文(要注意パターン)
SELECT id, customer, amount
FROM orders
WHERE amount IS NULL
   OR amount < 1000;

-- 結果: id=7(600円)と id=8(NULL)

-- amount が入力済みでかつ 5000 円以上
SELECT id, customer, amount
FROM orders
WHERE amount IS NOT NULL
  AND amount >= 5000;
NULL を OR で追加する場合の注意:
WHERE amount < 1000 だけでは NULL の行は除外されます(NULL < 1000 = UNKNOWN)。NULL の行も含めたい場合は WHERE amount IS NULL OR amount < 1000 と明示してください。
顧客ごとの複合条件(自己参照 / サブクエリ)
-- 田中さんの注文で amount が田中さんの平均以上のもの
SELECT id, customer, amount
FROM orders
WHERE customer = '田中'
  AND amount >= (
      SELECT AVG(amount)
      FROM orders
      WHERE customer = '田中'
  );

-- 1 件でも家電注文がある顧客の全注文(EXISTS)
SELECT o.id, o.customer, o.category
FROM orders AS o
WHERE EXISTS (
    SELECT 1 FROM orders AS o2
    WHERE o2.customer = o.customer
      AND o2.category = '家電'
)
ORDER BY o.customer, o.id;

NOT IN の NULL 問題(重大な落とし穴)

NOT IN のリストや サブクエリの結果に NULL が 1 つでも含まれると全件 0 件になります。これは三値論理の仕様によるもので、非常に多くの開発者が踏む落とし穴です。

NOT IN の NULL 問題と対処法
-- NG: amount に NULL が含まれるリストで NOT IN を使うと 0 件になる
SELECT * FROM orders
WHERE id NOT IN (1, 2, NULL);
-- 解説: id <> 1 AND id <> 2 AND id <> NULL
--        id <> NULL は UNKNOWN → 全条件が UNKNOWN → 全件除外 → 0件!

-- OK: NULL を除外した明示的なリストを使う
SELECT * FROM orders
WHERE id NOT IN (1, 2);  -- NULL を含まないリスト

-- サブクエリで NULL が混入するケース(より危険)
-- amount が NULL の行を持つテーブルをサブクエリにすると全件 0 件
SELECT * FROM orders
WHERE id NOT IN (
    SELECT id FROM orders WHERE status = 'canceled'
);
-- ↑ このサブクエリが NULL を返す可能性があれば 0 件になる

-- 安全な代替: NOT EXISTS を使う(NULL に強い)
SELECT * FROM orders AS o
WHERE NOT EXISTS (
    SELECT 1 FROM orders AS o2
    WHERE o2.id = o.id
      AND o2.status = 'canceled'
);
NOT IN vs NOT EXISTS の使い分け:

  • NOT IN: リストが確実に NULL を含まない場合は使える(シンプルで読みやすい)
  • NOT EXISTS: サブクエリが NULL を返す可能性がある場合は常に安全
  • LEFT JOIN + IS NULL: 大量データでのパフォーマンスが NOT EXISTS より優れる場合がある
  • NOT 条件全般の詳細は一致しないデータを抽出する方法を参照

HAVING と WHERE の使い分け(集計後の絞り込み)

WHERE は個々の行を集計前に絞り込む、HAVING はGROUP BY 後のグループを集計結果で絞り込むという違いがあります。

WHERE と HAVING の使い分け
-- WHERE: 集計前の行絞り込み(status が shipped の行だけで集計)
SELECT customer, COUNT(*) AS 件数, SUM(amount) AS 合計
FROM orders
WHERE status = 'shipped'              -- ← 先に行を絞り込む
GROUP BY customer;

-- HAVING: 集計後のグループ絞り込み(合計が 10000 以上のグループのみ)
SELECT customer, COUNT(*) AS 件数, SUM(amount) AS 合計
FROM orders
GROUP BY customer
HAVING SUM(amount) >= 10000;          -- ← 集計結果で絞り込む

-- 両方の組み合わせ(shipped のみを集計し、合計 5000 以上のグループを返す)
SELECT customer, COUNT(*) AS 件数, SUM(amount) AS 合計
FROM orders
WHERE status = 'shipped'              -- ① 先に shipped に絞る
GROUP BY customer
HAVING SUM(amount) >= 5000;           -- ② 集計後にさらに絞る
WHERE HAVING
処理タイミング GROUP BY より前(行単位) GROUP BY より後(グループ単位)
集計関数(SUM・COUNT等) 使えない 使える
インデックス活用 効く(行を早期に絞り込む) 効きにくい(集計後の絞り込み)
典型的な用途 特定ステータス・期間の絞り込み 件数・合計値が閾値以上のグループ
パフォーマンスのコツ:

  • WHERE でできる絞り込みは WHERE でやる(集計対象を事前に減らすと GROUP BY が速くなる)
  • HAVING に書けるものを WHERE に書いてしまうとインデックスが使われない場合がある
  • HAVING COUNT(*) >= 2 など集計関数を使った条件のみ HAVING に書く

SARGable 条件とインデックスの活用

インデックスが効く条件の書き方を SARGable(Search ARGument ABLE)条件と呼びます。WHERE 句の書き方次第でインデックスが使われなくなり、全件スキャンになることがあります。

状況 SARGable でない書き方(遅い) SARGable な書き方(速い)
列に関数を適用 YEAR(order_date) = 2024 order_date BETWEEN '2024-01-01' AND '2024-12-31'
計算式を列側に書く amount * 1.1 >= 11000 amount >= 10000
暗黙の型変換 id = '100'(id が INT 型) id = 100(型を合わせる)
LIKE の前方ワイルドカード LIKE '%田' LIKE '田%'(前方一致のみインデックス有効)
OR で異なる列を使う col1 = 'A' OR col2 = 'B' UNION で分けるか複合インデックスを検討
SARGable 条件の書き直し例
-- NG: 列に関数をかけるとインデックスが使われない
SELECT * FROM orders
WHERE YEAR(order_date) = 2024
  AND MONTH(order_date) = 3;

-- OK: 範囲比較に書き直す
SELECT * FROM orders
WHERE order_date >= '2024-03-01'
  AND order_date <  '2024-04-01';

-- NG: 計算式を列側に書くとインデックスが使われない
SELECT * FROM orders WHERE amount / 2 >= 5000;

-- OK: 定数側を計算する
SELECT * FROM orders WHERE amount >= 10000;

-- EXPLAIN で実行計画を確認して type が ALL かどうかチェックする
EXPLAIN SELECT * FROM orders WHERE order_date >= '2024-03-01';

複数条件が意図通りか確認するデバッグテクニック

条件を分割して確認する
-- 複雑な条件を段階的に確認する手順

-- ステップ1: 件数だけ確認(全件)
SELECT COUNT(*) FROM orders;  -- 8件

-- ステップ2: 条件Aだけ確認
SELECT COUNT(*) FROM orders WHERE status = 'shipped';  -- 4件

-- ステップ3: 条件Aとの AND を確認
SELECT COUNT(*) FROM orders
WHERE status = 'shipped' AND amount >= 10000;  -- 1件

-- ステップ4: 期待件数と合っているか確認
-- → 多すぎ/少なすぎなら括弧の位置や OR/AND の見直し

-- テクニック: 条件ごとにフラグを出して可視化
SELECT
    id,
    customer,
    status,
    amount,
    CASE WHEN status = 'shipped'   THEN '✓' ELSE '✗' END AS 配送済み,
    CASE WHEN amount >= 10000      THEN '✓' ELSE '✗' END AS 高額
FROM orders;
NULL 絡みの条件が疑わしいときの確認方法:
SELECT COUNT(*), COUNT(列名) FROM テーブル で NULL の件数を把握してから条件を組む。COUNT(*) は全行数、COUNT(列名) は NULL 除外の件数を返すので、差が NULL 件数です。

よくある質問(FAQ)

QWHERE 句に AND と OR を混ぜると期待通りに絞り込めません。どうすればよいですか?
AAND は OR より優先度が高いため、A OR B AND CA OR (B AND C) と解釈されます。意図した通りにグループ化するには括弧 () を使ってください。例えば「(家電 または 食品)かつ 1 万円以上」は (category = '家電' OR category = '食品') AND amount >= 10000 と書きます。EXPLAIN で実行計画を確認しながら条件をステップごとに確認するのが効果的です。
QWHERE status != ‘canceled’ としているのに NULL のステータスの行まで除外されています。
ANULL との比較(NULL != 'canceled')は TRUE でも FALSE でもなく UNKNOWN になります。UNKNOWN の行は WHERE で除外されるため、NULL 行も消えてしまいます。NULL の行を含めるには WHERE status != 'canceled' OR status IS NULL と明示的に条件を追加してください。
QNOT IN を使うと期待と違う結果(0件)になります。
ANOT IN のリストや サブクエリの結果に NULL が 1 件でも含まれると、全件 0 件になります。これは三値論理の仕様です(x <> NULL = UNKNOWN で全条件が UNKNOWN になる)。安全な代替として NOT EXISTS を使うか、サブクエリに WHERE 列 IS NOT NULL を加えて NULL を除外してください。
QWHERE で SUM() が使えないとエラーになりました。
AWHERE は GROUP BY より前に処理されるため、集計関数(SUM・COUNT・AVG など)は使えません。グループの集計結果を条件にしたい場合は HAVING を使ってください。例: GROUP BY customer HAVING SUM(amount) >= 10000
Q条件が多いときに見やすく書くコツはありますか?
A次のような書き方が読みやすいです:① 各条件を 1 行ずつ書いて AND / OR を行頭に揃える。② OR のグループは括弧で囲んで一段インデントする。③ 複雑すぎる場合は CTE(WITH 句)でステップに分けてサブクエリ化する。④ コメント(-- shipped かつ高額)を添えて意図を明記する。条件が 5 つ以上になる場合は複数の CTE に分割することで可読性が大幅に上がります。

まとめ

やりたいこと 書き方・ポイント
両方の条件を満たす行 WHERE A AND B
どちらかの条件を満たす行 WHERE A OR B(AND との混在時は括弧必須)
条件を満たさない行 WHERE NOT A または WHERE A <> 値
AND と OR の優先順位を制御 括弧 () で明示的にグループ化
NULL の行を含める IS NULL を OR で明示的に追加
NOT IN で全件 0 件になる リストに NULL が含まれていないか確認、または NOT EXISTS を使う
集計結果で絞り込む WHERE ではなく HAVING を使う
インデックスを効かせる 列に関数や計算を適用しない(SARGable 条件)
条件が意図通りか確認 条件を 1 つずつ段階的に追加して件数を確認

各演算子の詳細についてはIN・BETWEEN・EXISTS 完全ガイドNOT 条件完全ガイドNULL フィルタリング完全ガイドもあわせてご覧ください。