SQL 述語(Predicate) — 比較・BETWEEN・IS NULLの基礎

基礎述語 (Predicate)BETWEEN / LIKEIS NULL / INEXISTS / NOT EXISTS複合述語PostgreSQL対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 1

比較述語 — =, >=, <= と AND を組み合わせて価格・在庫で絞り込む

比較述語AND / OR複合条件在庫分析
前提知識

比較述語(= , <> , < , > , <= , >=)はSQLの最も基本的な述語です。複数の条件は AND(すべて真)または OR(いずれか真)で連結します。

SELECT * FROM products
WHERE  price >= 10000   -- 下限(境界値を含む)
  AND  price <= 100000  -- 上限(境界値を含む)
  AND  stock >= 1;       -- 在庫あり
AND は「論理積」:連結したすべての条件が TRUE のときのみ行が残ります。1つでも FALSE/UNKNOWN があると行は除外されます。OR(論理和)はいずれか1つがTRUEで残ります。
問題

products テーブルから、price が 10,000 以上 100,000 以下、かつ stock が 1 以上(在庫あり)の商品を取得してください。取得列は product_id, name, category, price, stock、price 降順で返してください。

使用テーブル
▸ products
product_idnamecategorypricestock
1スマートフォン Xスマートフォン8980050
2スマートブック ProPC1280000
3タブレット Airタブレット6480030
4ワイヤレスイヤホンアクセサリ12800100
5USBケーブルアクセサリ980200
6ゲーミングPC ProPC1980005
期待出力
product_idnamecategorypricestock
1スマートフォン Xスマートフォン8980050
3タブレット Airタブレット6480030
4ワイヤレスイヤホンアクセサリ12800100
模範解答コード
SELECT
  product_id, name, category, price, stock
FROM   products
WHERE  price >= 10000    -- 下限:10,000円以上(境界値を含む)
  AND  price <= 100000   -- 上限:100,000円以下(境界値を含む)
  AND  stock >= 1         -- 在庫あり(stock が 0 より大きい)
ORDER BY price DESC;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM products                 → 6行読み込み
  2. WHERE price / stock の範囲条件     → 3行に絞り込み
  3. SELECT product_id, name, ...  → 5列を選択
  4. ORDER BY price DESC           → 価格降順に並び替え
  */
解説(テーブル変化・ポイント)
SELECT product_id, name, category, price, stock FROM products WHERE price >= 10000 AND price <= 100000 AND stock >= 1 ORDER BY price DESC;
LEGEND
データ取得・読込対象
① FROM products
FROM productsproductsテーブル全6行を読み込みます。これがWHERE句で評価される入力データです。
1 / 3
product_idnamepricestock
1スマートフォン X8980050
2スマートブック Pro1280000
3タブレット Air6480030
4ワイヤレスイヤホン12800100
5USBケーブル980200
6ゲーミングPC Pro1980005
全 6行 読込
学習ポイント
AND は「論理積」:連結した全条件が TRUE のときのみ行が残ります。どれか1つでも FALSE / UNKNOWN があると除外。OR(論理和)はいずれか1条件がTRUEで残ります。
境界値の含み方:>= は境界値を含む(10000以上 → 10000も対象)、> は含まない(10000超 → 10001以上が対象)。仕様書の「以上・超・以下・未満」を正確に読むことが重要です。
BETWEEN との等価性:price >= 10000 AND price <= 100000price BETWEEN 10000 AND 100000 と等価です。BETWEEN は両端を含みます(Q2で詳解)。
アンチパターン
AND と OR の混用ミス:WHERE category = 'PC' AND category = 'タブレット' は「PCかつタブレット」を意味し1列が同時に2値を持つことはないため常に0件。カテゴリの複数指定には OR または IN を使います(Q5参照)。
NULL との比較:WHERE stock = NULL は UNKNOWN となり常に0件です。NULL判定には必ず IS NULL を使います(Q4で詳解)。
実務コラム:インデックスと述語の絞り込み率
実務では WHERE 句の各条件にインデックスが効いているか EXPLAIN で確認します。絞り込み率(selectivity)が高い条件(例: price の範囲)にインデックスを貼ると高速化しやすいです。stock >= 1 のように大半の行がTRUEになる低selectivity条件はインデックス効果が低く、オプティマイザがフルスキャンを選ぶ場合があります。
QUESTION 2

BETWEEN述語 — 日付範囲指定と TIMESTAMP 型での境界値の罠

BETWEEN範囲条件日付フィルタ境界値注意
前提知識

BETWEEN a AND b は col >= a AND col <= b と等価で、両端の境界値を含む範囲指定です。数値・日付・文字列のすべてに使えます。

SELECT * FROM orders
WHERE  ordered_at BETWEEN '2024-01-01' AND '2024-03-31';
-- ↑ ordered_at >= '2024-01-01' AND ordered_at <= '2024-03-31' と等価
DATE型 vs TIMESTAMP型の罠:カラムが TIMESTAMP 型の場合、'2024-03-31''2024-03-31 00:00:00' と解釈されます。3月31日 00:01 以降の行が除外されるため、TIMESTAMP型には ordered_at < '2024-04-01' を使うほうが安全です。
問題

orders テーブルから、ordered_at が 2024年1月1日〜2024年3月31日(Q1期間)の注文を取得してください。取得列は order_id, user_id, amount, status, ordered_at、ordered_at 昇順で返してください。

使用テーブル
▸ orders
order_iduser_idamountstatusordered_at
10118000completed2024-01-15
10223500completed2024-02-20
103112000pending2024-04-05
10439500completed2023-12-01
10546000cancelled2024-03-10
10634500completed2024-01-28
期待出力
order_iduser_idamountstatusordered_at
10118000completed2024-01-15
10634500completed2024-01-28
10223500completed2024-02-20
10546000cancelled2024-03-10
模範解答コード
SELECT
  order_id, user_id, amount, status, ordered_at
FROM   orders
WHERE  ordered_at BETWEEN '2024-01-01' AND '2024-03-31'
--                ↑ >= '2024-01-01' AND <= '2024-03-31' と等価
--                  DATE型なら両端を含む。TIMESTAMP型は '2024-03-31 00:00:00' が上限になる点に注意。
ORDER BY ordered_at;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM orders                            → 6行読み込み
  2. WHERE ordered_at BETWEEN ...           → 4行に絞り込み
  3. SELECT order_id, user_id, amount, ...  → 5列を選択
  4. ORDER BY ordered_at                    → 日付昇順
  */
解説(テーブル変化・ポイント)
SELECT order_id, user_id, amount, status, ordered_at FROM orders WHERE ordered_at BETWEEN '2024-01-01' AND '2024-03-31' ORDER BY ordered_at;
LEGEND
データ取得・読込対象
① FROM orders
FROM ordersordersテーブル全6行を読み込みます。ordered_at の値に注目してください。2023年・2024年Q1・3グループが混在しています。
1 / 4
order_iduser_idamountstatusordered_at
10118000completed2024-01-15
10223500completed2024-02-20
103112000pending2024-04-05
10439500completed2023-12-01
10546000cancelled2024-03-10
10634500completed2024-01-28
全 6行 読込
学習ポイント
BETWEEN は両端を含む:BETWEEN a AND bcol >= a AND col <= b と完全に等価です。a と b の境界値もヒットすることを必ず覚えましょう。
TIMESTAMP型では上限が 00:00:00 になる:カラムが TIMESTAMP 型の場合、'2024-03-31''2024-03-31 00:00:00' として解釈されます。3月31日 00:01 以降の注文が漏れるバグが起きます。TIMESTAMP型には ordered_at < '2024-04-01' パターンを使いましょう。
NOT BETWEEN も使える:NOT BETWEEN a AND bcol < a OR col > b と等価で、「範囲外の行を取得する」逆条件を書けます。
アンチパターン
上限・下限を逆に書く:BETWEEN 100000 AND 10000 は数学的に成立しないため常に0件。必ず 小さい値 AND 大きい値 の順で書きます。
TIMESTAMP型で月末日を上限にする:BETWEEN '2024-01-01' AND '2024-03-31' でタイムスタンプ型カラムの3月31日分が漏れるバグが頻発します。< '2024-04-01' に変えるだけで解決します。
実務コラム:月次・四半期レポートクエリでの安全な日付範囲指定
TIMESTAMP型カラムでは >= '月初' AND < '翌月初' パターンが実務標準です(例:2月は >= '2024-02-01' AND < '2024-03-01')。うるう年も自動的に考慮され、時刻の切り捨てが不要なため最も安全です。BETWEENは DATE 型カラムに限定して使うのが無難です。
QUESTION 3

LIKE述語 — ワイルドカードによるパターンマッチング(前方・後方・中間一致)

LIKEパターンマッチ文字列検索商品名フィルタ
前提知識

LIKE述語は文字列のパターンマッチングに使います。%(パーセント)は0文字以上の任意文字列、_(アンダースコア)は任意の1文字に一致します。

WHERE name LIKE 'スマート%'  -- 前方一致:「スマート」で始まる
   OR name LIKE '%Pro'      -- 後方一致:「Pro」で終わる
   OR name LIKE '%Air%'     -- 中間一致:「Air」を含む(インデックス不可)
   OR name LIKE '__ブレット';  -- _は任意の1文字(2文字+ブレット)
パフォーマンスの注意:前方一致 'prefix%' はB-treeインデックスが効きます。後方一致 '%suffix' と中間一致 '%word%' はインデックスが使えずフルスキャンになります。大規模テーブルでは要注意です。
問題

products テーブルから、name が「スマート」で始まる商品、またはname が「Pro」で終わる商品を取得してください。取得列は product_id, name, category, price、price 昇順で返してください。

使用テーブル
▸ products
product_idnamecategoryprice
1スマートフォン Xスマートフォン89800
2スマートブック ProPC128000
3タブレット Airタブレット64800
4ワイヤレスイヤホンアクセサリ12800
5USBケーブルアクセサリ980
6ゲーミングPC ProPC198000
期待出力
product_idnamecategoryprice
1スマートフォン Xスマートフォン89800
2スマートブック ProPC128000
6ゲーミングPC ProPC198000
模範解答コード
SELECT
  product_id, name, category, price
FROM   products
WHERE  name LIKE 'スマート%'  -- 前方一致:先頭が「スマート」
    OR name LIKE '%Pro'       -- 後方一致:末尾が「Pro」
ORDER BY price;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM products                 → 6行読み込み
  2. WHERE name LIKE ... OR ...    → 3行に絞り込み
  3. SELECT product_id, name, ...  → 4列を選択
  4. ORDER BY price                → 価格昇順
  */
解説(テーブル変化・ポイント)
SELECT product_id, name, category, price FROM products WHERE name LIKE 'スマート%' OR name LIKE '%Pro' ORDER BY price;
LEGEND
データ取得・読込対象
① FROM products
FROM productsproductsテーブル全6行を読み込みます。name列のパターンに注目してください。
1 / 3
product_idnamecategoryprice
1スマートフォン Xスマートフォン89800
2スマートブック ProPC128000
3タブレット Airタブレット64800
4ワイヤレスイヤホンアクセサリ12800
5USBケーブルアクセサリ980
6ゲーミングPC ProPC198000
全 6行 読込
学習ポイント
ワイルドカードの意味:%(0文字以上の任意文字列)と _(任意の1文字)の2種類があります。例:'A_C' は「ABC」「AXC」に一致しますが、「AC」「ABBC」には不一致。
インデックスの効き方:前方一致 'prefix%' はB-treeインデックスが使えます。後方一致 '%suffix' と中間一致 '%word%' はインデックスが効かずフルスキャンになります。全文検索が必要な場合は pg_trgm(PostgreSQL)や FULLTEXT INDEX(MySQL)の利用を検討しましょう。
NOT LIKE で除外条件を書く:WHERE name NOT LIKE '%Pro' で「Proで終わらない商品」を抽出できます。複数パターンを除外したい場合は NOT LIKE '...' AND NOT LIKE '...' と AND でつなぎます。
アンチパターン
LIKE に = と同じ感覚でワイルドカードなしで使う:LIKE 'スマートフォン X'= 'スマートフォン X' と等価ですが、インデックスの効き方が異なりオプティマイザが最適化しにくい場合があります。完全一致には = を使いましょう。
大量テーブルへの中間一致:LIKE '%keyword%' は数百万行のテーブルに対してはフルスキャンになります。運用テーブルへの実行前に EXPLAIN で確認し、必要なら全文検索インデックスを検討してください。
実務コラム:あいまい検索とSQLインジェクション
ユーザー入力をLIKE条件に使う場合、%_ がそのまま含まれるとパターンが意図せず広がります。アプリ側で %%__ にエスケープし、LIKE :q ESCAPE '' と ESCAPE 句を使うのが正しい実装です。プレースホルダを使わずに文字列を直接連結するとSQLインジェクションの危険があります。
QUESTION 4

IS NULL述語 — 三値論理と NULL 判定の正しい書き方

IS NULL三値論理NULL処理NULLは= で比較不可
前提知識

SQLは TRUE / FALSE の2値ではなく TRUE / FALSE / UNKNOWN の三値論理で動きます。NULL は「不明な値」であり、NULL = NULLNULL <> NULL もすべて UNKNOWN になります。NULLの判定には必ず IS NULL / IS NOT NULL を使います。

-- ✗ 間違い:NULLに = は使えない(結果は常にUNKNOWN → 0件)
WHERE coupon_code = NULL

-- ✓ 正しい:IS NULL で判定する
WHERE coupon_code IS NULL

-- ✓ NULLでない行を取得
WHERE coupon_code IS NOT NULL
WHERE の評価と三値論理:WHERE 句では TRUE の行のみが残ります。FALSE と UNKNOWN はどちらも行を除外します。NOT UNKNOWN も UNKNOWN のままです。
問題

orders テーブルから、coupon_code が NULL(クーポン未使用)の注文を取得してください。取得列は order_id, user_id, amount, coupon_code, ordered_at、order_id 昇順で返してください。

使用テーブル
▸ orders
order_iduser_idamountcoupon_codeordered_at
10118000SUMMER102024-06-01
102212000NULL2024-06-03
10335000NULL2024-06-05
10419800WINTER202024-06-07
10543200NULL2024-06-10
期待出力
order_iduser_idamountcoupon_codeordered_at
102212000NULL2024-06-03
10335000NULL2024-06-05
10543200NULL2024-06-10
模範解答コード
SELECT
  order_id, user_id, amount, coupon_code, ordered_at
FROM   orders
WHERE  coupon_code IS NULL   -- NULL判定は必ず IS NULL(= NULL は常にUNKNOWN)
ORDER BY order_id;

/*
  実行順序(SQLの論理的な評価順):
  1. FROM orders                    → 5行読み込み
  2. WHERE coupon_code IS NULL      → 3行に絞り込み
  3. SELECT order_id, user_id, ...  → 5列を選択
  4. ORDER BY order_id              → order_id 昇順

  ▸ 三値論理メモ:
      'SUMMER10' IS NULL  → FALSE  → 除外
      NULL       IS NULL  → TRUE   → 通過
      NULL = NULL         → UNKNOWN → 除外(これがよくある間違い)
*/
解説(テーブル変化・ポイント)
SELECT order_id, user_id, amount, coupon_code, ordered_at FROM orders WHERE coupon_code IS NULL ORDER BY order_id;
LEGEND
データ取得・読込対象
① FROM orders
FROM ordersordersテーブル全5行を読み込みます。coupon_code列にNULLと文字列が混在しています。
1 / 3
order_iduser_idamountcoupon_codeordered_at
10118000SUMMER102024-06-01
102212000NULL2024-06-03
10335000NULL2024-06-05
10419800WINTER202024-06-07
10543200NULL2024-06-10
全 5行 読込
学習ポイント
SQLの三値論理(3VL):SQL は TRUE / FALSE / UNKNOWN の三値論理です。NULL は「不明」を意味し、NULLとの比較は常に UNKNOWN になります。WHERE 句はTRUEの行のみを通過させるため、UNKNOWN は FALSE と同じく除外されます。
IS NULL と IS NOT NULL:IS NULL は唯一NULLを正しく検出できる述語です。IS NOT NULL は「値が存在する行」を取得します。COALESCE関数と組み合わせて COALESCE(coupon_code, 'なし') のようにNULLをデフォルト値に変換する書き方も頻出です。
NOT IN と NULL の危険な組み合わせ:IN / NOT IN のリストに NULL が含まれると評価が UNKNOWN になり意図しない結果が生じます(Q6で詳解)。NULL が混入しうる列を NOT IN に使う場合は必ず WHERE col IS NOT NULL を内側に追加しましょう。
アンチパターン
WHERE col = NULL:最も多い誤りです。col = NULL は常に UNKNOWN → 0件。必ず IS NULL を使います。
WHERE col != NULL / <> NULL:これも同様に常に UNKNOWN → 0件。「NULLでない行」は IS NOT NULL で取得します。
実務コラム:NULLの意味論を設計段階で決める
NULLには「未入力」「不明」「該当なし」など複数の意味がアプリによって混在しがちです。設計段階でNULLの意味を定義し、カラムコメントに明記するのが重要です。例えば coupon_code の NULL は「クーポン未使用」を意味すると明確にしておけば、アプリ側もSQLも迷いません。意味が曖昧なNULLは後々のバグの温床になります。
QUESTION 5

IN述語(固定リスト) — 複数値を OR なしにスマートに絞り込む

IN固定リストカテゴリ絞り込みOR 代替
前提知識

WHERE col IN (v1, v2, …) は col = v1 OR col = v2 OR … の省略形です。値を列挙してシンプルに複数一致を表現できます。

WHERE category IN ('PC', 'タブレット')
-- ↑ category = 'PC' OR category = 'タブレット' と等価
IN の利点:OR の羅列より読みやすく、値を追加・削除するだけで条件を変更できます。また NOT IN (v1, v2, …) で「リスト以外の行」を取得できます。
問題

products テーブルから、category が 'スマートフォン' または 'タブレット' または 'PC'の商品を取得してください。取得列は product_id, name, category, price, stock、category 昇順 → price 昇順で返してください。

使用テーブル
▸ products
product_idnamecategorypricestock
1スマートフォン Xスマートフォン8980050
2スマートブック ProPC1280000
3タブレット Airタブレット6480030
4ワイヤレスイヤホンアクセサリ12800100
5USBケーブルアクセサリ980200
6ゲーミングPC ProPC1980005
期待出力
product_idnamecategorypricestock
2スマートブック ProPC1280000
6ゲーミングPC ProPC1980005
1スマートフォン Xスマートフォン8980050
3タブレット Airタブレット6480030
模範解答コード
SELECT
  product_id, name, category, price, stock
FROM   products
WHERE  category IN ('スマートフォン', 'タブレット', 'PC')
ORDER BY category, price;                      -- ↑ category = 'スマートフォン' OR category = 'タブレット' OR category = 'PC' と等価

/*
  実行順序(SQLの論理的な評価順):
  1. FROM products                 → 6行読み込み
  2. WHERE category IN (...)       → 4行に絞り込み
  3. SELECT product_id, name, ...  → 5列を選択
  4. ORDER BY category, price      → カテゴリ昇順→価格昇順
  */
解説(テーブル変化・ポイント)
SELECT product_id, name, category, price, stock FROM products WHERE category IN ('スマートフォン', 'タブレット', 'PC') ORDER BY category, price;
LEGEND
データ取得・読込対象
① FROM products
FROM productsproductsテーブル全6行を読み込みます。category列の値のバリエーションに注目してください。
1 / 4
product_idnamecategoryprice
1スマートフォン Xスマートフォン89800
2スマートブック ProPC128000
3タブレット Airタブレット64800
4ワイヤレスイヤホンアクセサリ12800
5USBケーブルアクセサリ980
6ゲーミングPC ProPC198000
全 6行 読込
学習ポイント
IN は OR の省略形:IN (v1, v2, v3)= v1 OR = v2 OR = v3 と等価です。値が増えても IN のリストを追加するだけで済み、OR の羅列より可読性が高くなります。
NOT IN で逆条件を書く:WHERE category NOT IN ('アクセサリ') は「アクセサリ以外の商品」を取得できます。ただし NOT IN のリストに NULL が含まれると全件0件になる罠があるため注意が必要です(Q6で詳解)。
IN の内側にサブクエリも使える:固定リストの代わりに IN (SELECT category FROM ... のようにサブクエリを使うと、テーブルの状態に応じて動的にリストを生成できます(Q6で詳解)。
アンチパターン
IN リストが数万件になる:アプリ側でIDリストを生成して IN (id1, id2, ... id50000) のような巨大リストをSQLに埋め込むと、パース・評価コストが激増します。一時テーブルや JOIN を使う設計に変えましょう。
IN の中に複数列を並べる:IN (col1, col2) は列のリストではなく値リストです。複数列の組み合わせ一致には (col1, col2) IN ((v1, v2), (v3, v4)) の行値コンストラクタ構文(PostgreSQL対応)を使います。
実務コラム:フィルタ条件のUI連携パターン
ECサイトのカテゴリフィルタなど、ユーザーが複数選択する場合はアプリ側で配列を受け取り、プレースホルダで WHERE category = ANY($1)(PostgreSQL)や WHERE category IN (?)(複数バインド)として動的に組み立てます。値の数が変動しても安全に対応できます。