SQL 述語(Predicate) — LIKE・BETWEEN・ANYの応用

応用述語 (Predicate) 応用LIKE / BETWEENIS NULL / ANY述語複数 EXISTSPostgreSQL対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 6

LIKE述語 × EXISTS述語 — パターンマッチと存在確認を組み合わせた実践検索

LIKE述語EXISTSパターンマッチユーザー検索
前提知識

LIKE述語は文字列のパターン一致を検査します。%(任意の0文字以上)と _(任意の1文字)の2種類のワイルドカードがあります。EXISTS と組み合わせることで「名前パターン × 購買実績あり」のような実務頻出の複合フィルタを実現できます。

-- % : 0文字以上の任意文字列
WHERE name LIKE '田%'    -- 「田」で始まる(前方一致)※インデックス有効
WHERE name LIKE '%田%'   -- 「田」を含む(中間一致)※インデックス不可
-- _ : 任意の1文字
WHERE code LIKE 'A_01'   -- A?01 形式(?は任意1文字)
-- ILIKE: 大文字小文字を区別しない(PostgreSQL固有)
WHERE email ILIKE '%@example.com'
前方一致('田%')のみインデックスが有効:中間・後方一致では B-Tree インデックスが使えず全スキャンになります。大量データには pg_trgm 拡張 + GIN インデックス または全文検索を使いましょう。
問題

users テーブルから、name が '田' で始まる(LIKE '田%')かつ status が 'completed' の注文が1件以上あるユーザーを取得してください。取得列は user_id, name, email、user_id 昇順で返してください。

使用テーブル
▶ users
user_idnameemailplan
1田中 太郎tanaka@example.compremium
2佐藤 花子sato@example.comstandard
3田村 一郎tamura@example.comfree
4山田 次郎yamada@example.compremium
5伊藤 三郎ito@example.compremium
▶ orders
order_iduser_idstatus
1011completed
1022completed
1033completed
1043completed
1054cancelled
1065pending
期待出力
user_idnameemail
1田中 太郎tanaka@example.com
3田村 一郎tamura@example.com
QUESTION 7

BETWEEN述語 × NOT EXISTS述語 — 価格帯フィルタと未販売商品の検出

BETWEENNOT EXISTS範囲フィルタ在庫・販売分析
前提知識

BETWEEN は範囲条件を簡潔に書ける述語です。a BETWEEN x AND ya >= x AND a <= y(両端を含む閉区間)と等価です。NOT EXISTS と組み合わせることで「価格帯 × 売上実績なし」のような在庫・販売分析クエリを実現できます。

-- BETWEEN: 両端(x, y)を含む閉区間
WHERE price BETWEEN 3000 AND 7000   -- >= 3000 AND <= 7000 と等価

-- NOT BETWEEN: 範囲外
WHERE price NOT BETWEEN 3000 AND 7000 -- < 3000 OR > 7000 と等価

-- 日付範囲での注意点(timestamp型)
WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31'
-- timestamp型では '2024-01-31 00:00:00' まで → 31日の注文が漏れる
-- 正: created_at >= '2024-01-01' AND created_at < '2024-02-01'
BETWEEN の可読性:上下限のセットが視覚的に明確で意図が伝わりやすいです。ただし日付・タイムスタンプ型では末日 00:00:00 までしか含まれない罠があるため、日付範囲には >= start AND < end+1 形式が安全です。
問題

products テーブルから、category が 'アクセサリ' かつ price が 3,000〜7,000(BETWEEN)の商品のうち、completed の注文が1件もない商品を NOT EXISTS で取得してください。取得列は product_id, name, price, stock、price 昇順で返してください。

使用テーブル
▶ products
product_idnamecategorypricestock
1ワイヤレスマウスアクセサリ350080
2メカニカルキーボードアクセサリ880045
3HDMIケーブル 2mアクセサリ1200150
4USBハブ 7ポートアクセサリ420030
5外付けSSD 1TBストレージ980020
6Webカメラ HDアクセサリ650015
▶ orders
order_idproduct_idstatus
1014completed
1026pending
1031cancelled
期待出力
product_idnamepricestock
1ワイヤレスマウス350080
6Webカメラ HD650015
QUESTION 8

IS NULL述語 × IN サブクエリ — NULL安全な絞り込みと動的リストフィルタ

IS NULLIN サブクエリNULL安全三値論理
前提知識

SQLの三値論理(TRUE / FALSE / UNKNOWN)では、NULL との比較はすべて UNKNOWN を返します。WHERE 句では UNKNOWN は FALSE と同様に行が除外されます。NULL かどうかを調べるには IS NULL / IS NOT NULL の使用が必須です。

-- ✗ = NULL は常に UNKNOWN(絶対に使わない)
WHERE shipped_at = NULL     -- 常に UNKNOWN → 全行除外(意図と真逆)
WHERE shipped_at <> NULL    -- 常に UNKNOWN → 全行除外

-- ✓ IS NULL / IS NOT NULL が唯一正しい書き方
WHERE shipped_at IS NULL     -- NULL の行のみ残す(未発送検出)
WHERE shipped_at IS NOT NULL -- NULL 以外の行のみ(発送済み)

-- NULL は演算に伝播する
SELECT amount + discount_amount         -- discount が NULL → 結果も NULL
SELECT COALESCE(discount_amount, 0)   -- COALESCE で NULL を代替値に変換
三値論理の本質:NULL = NULL の評価結果は TRUE ではなく UNKNOWN です。「NULL 同士は等しい」とは見なされません。PostgreSQL では NULL IS NOT DISTINCT FROM NULL で NULL 同士を等しいとして比較できます。
問題

orders テーブルから、status が 'processing' かつ shipped_at が NULL(未発送)の注文のうち、plan が 'premium' のユーザーの注文を IN サブクエリで取得してください。取得列は order_id, user_id, amount, status、amount 降順で返してください。

使用テーブル
▶ orders
order_iduser_idamountstatusshipped_at
10118000completed2024-01-15
10225000processingNULL
103112000processingNULL
10433000completed2024-01-20
10549500processing2024-01-25
10637000processingNULL
▶ users
user_idnameplan
1田中 太郎premium
2佐藤 花子premium
3鈴木 一郎standard
4山田 次郎free
期待出力
order_iduser_idamountstatus
103112000processing
10225000processing
QUESTION 9

ANY述語 / ALL述語 — 複数値との部分比較・全件比較(競合価格分析)

ANY述語ALL述語複数値比較競合分析
前提知識

ANY述語(SOME とも書ける)は「サブクエリの値のうちいずれか1つとの比較が TRUE なら TRUE」を返します。ALL述語は「サブクエリの値すべてとの比較が TRUE なら TRUE」を返します。

-- ANY: いずれか1つとの比較がTRUE → TRUE(OR の連鎖に相当)
WHERE price < ANY (SELECT price FROM competitor)
-- ↑ 等価: WHERE price < (SELECT MAX(price) FROM competitor)

-- ALL: すべての値との比較がTRUE → TRUE(AND の連鎖に相当)
WHERE price > ALL (SELECT price FROM competitor)
-- ↑ 等価: WHERE price > (SELECT MAX(price) FROM competitor)

-- 特殊な等価変換
WHERE col =  ANY (list)  -- = IN (list) と等価
WHERE col <> ALL (list)  -- = NOT IN (list) と等価(NULLの罠あり)
ALL + NULL の罠:ALL のリストに NULL が含まれると val <> NULL が UNKNOWN になり ALL 全体が FALSE になります。<> ALL(= NOT IN 相当)は NULL 混入で全行除外される問題があるため、実務では NOT EXISTS を優先しましょう。
問題

products(自社)テーブルから、同一カテゴリの競合製品(competitor_prices テーブル)の価格のいずれかより安い商品< ANY で取得してください。取得列は product_id, name, price、price 昇順。(解説タブで ALL バリアントも確認してください)

使用テーブル
▶ products(自社)
product_idnamecategoryprice
1モデルA ヘッドフォンオーディオ12000
2モデルB ヘッドフォンオーディオ8500
3モデルC ヘッドフォンオーディオ15000
4モデルD ヘッドフォンオーディオ6000
▶ competitor_prices(競合)
comp_idnamecategoryprice
1競合X ヘッドフォンオーディオ9000
2競合Y ヘッドフォンオーディオ11000
3競合Z ヘッドフォンオーディオ13500
期待出力
product_idnameprice
4モデルD ヘッドフォン6000
2モデルB ヘッドフォン8500
1モデルA ヘッドフォン12000
QUESTION 10

複数EXISTS の応用総合 — AND EXISTS + NOT EXISTS による精密な行選択

複数EXISTSAND EXISTS + NOT EXISTS実務総合ユーザー行動分析
前提知識

複数の EXISTS / NOT EXISTS を AND で連結することで「条件Aを満たす行が存在し、かつ条件Bを満たす行は存在しない」という精密な行選択が実現できます。これは実務の行動分析・セグメント抽出で最も頻出するパターンのひとつです。

-- 複数 EXISTS を AND で連結
WHERE EXISTS (               -- ① 条件A を満たす行が存在する
  SELECT 1 FROM orders o1
  WHERE  o1.user_id = u.user_id
    AND  o1.status = 'completed'
)
AND NOT EXISTS (             -- ② 条件B を満たす行は存在しない
  SELECT 1 FROM orders o2
  WHERE  o2.user_id = u.user_id
    AND  o2.status = 'cancelled'
)
複数EXISTS の威力:「completedの注文は存在するが、cancelledの注文は存在しない優良ユーザー」のような複雑な行動パターンを、サブクエリを追加するだけでシンプルに表現できます。JOIN + 集計では難しい条件も EXISTS の組み合わせで直感的に書けます。
問題

users テーブルから、plan が 'premium' または 'standard' のユーザーのうち、status が 'completed' の注文が1件以上あり、かつ status が 'cancelled' の注文が1件もないユーザーを取得してください。取得列は user_id, name, plan、user_id 昇順で返してください。

使用テーブル
▶ users
user_idnameplan
1田中 太郎premium
2佐藤 花子standard
3鈴木 一郎premium
4山田 次郎free
5伊藤 三郎standard
▶ orders
order_iduser_idstatus
1011completed
1021cancelled
1032completed
1043completed
1053pending
1065pending
期待出力
user_idnameplan
2佐藤 花子standard
3鈴木 一郎premium