SQL サブクエリ — CASE×SQ・複数EXISTS・多段CTEの応用

応用サブクエリCASE × サブクエリ複数 EXISTSTop-N 相関SQ多段CTEPostgreSQL対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 6

CASE式 × サブクエリ — 全体平均をCROSS JOINで取得してユーザーを3段階に分類する

CASE+SQCROSS JOINFROM句SQユーザーセグメント
前提知識

サブクエリをCASE式の閾値として使うと、データ主導の動的分類が可能になります。1行しか返さないサブクエリをCROSS JOINすることで、全行に同じ集計値(定数)を効率よく付与できます。

SELECT  s.user_id, s.total_spent, g.overall_avg,
  CASE
    WHEN s.total_spent >= 2 * g.overall_avg THEN 'HIGH_VALUE'
    WHEN s.total_spent >= g.overall_avg     THEN 'NORMAL'
    ELSE                                          'LIGHT'
  END AS segment
FROM (...) AS s        -- ユーザー別集計(FROM句SQ)
CROSS JOIN (...) AS g; -- 全体平均(1行)を全行に付与
CROSS JOIN が有効な理由:1行しか返さないサブクエリをCROSS JOINすると直積は「元の行数×1=元の行数」。全行に同じ集計値を1回の評価で付与できます。CASE WHEN に毎回スカラーSQを書くと同じ集計が複数回評価されますが、このパターンは評価が1回で済みます。
問題

orders の completed注文合計(total_spent)を持つユーザーを全体平均基準で HIGH_VALUE / NORMAL / LIGHT に分類してください。取得列は user_id, name, plan, total_spent, overall_avg, segment、total_spent降順。completedなしのユーザーは除外します。

使用テーブル
▸ users
user_idnameplan
1田中 太郎premium
2佐藤 花子free
3鈴木 一郎premium
4山田 次郎standard
5伊藤 三郎free
▸ orders
order_iduser_idamountstatus
10118000completed
102112000completed
10323500pending
10439500completed
10534000completed
10642000cancelled
10754500completed
10823000completed
集計対象と比較単位を整理してから、サブクエリの返す値を決めてください。
期待出力
user_idnameplantotal_spentoverall_avgsegment
1田中 太郎premium200006833HIGH_VALUE
3鈴木 一郎premium135006833NORMAL
5伊藤 三郎free45006833LIGHT
2佐藤 花子free30006833LIGHT
QUESTION 7

複数EXISTS連結 — AND/NOT EXISTSを組み合わせてターゲット顧客を絞り込む

複数EXISTSNOT EXISTS相関SQキャンペーン対象
前提知識

WHERE 句に EXISTS / NOT EXISTS を AND で連結することで、複数の独立した条件をチェーンできます。各 EXISTS は完全に独立した相関サブクエリとして評価されるため、「Aを持ち、かつBを持ち、かつCを持たない」という複合フィルタを実現できます。

SELECT u.user_id, u.name
FROM   users u
WHERE  EXISTS     (SELECT 1 FROM orders o1 WHERE o1.user_id = u.user_id AND ...)
  AND  EXISTS     (SELECT 1 FROM orders o2 WHERE o2.user_id = u.user_id AND ...)
  AND  NOT EXISTS (SELECT 1 FROM orders o3 WHERE o3.user_id = u.user_id AND ...);
AND の短絡評価(Short-circuit):最初の EXISTS が FALSE になった時点でその行への後続条件の評価は省略されます。user4 は ① で FALSE になるため、② ③ は評価されません。実行コストの低い条件を先頭に置くと効率が上がります。
問題

users テーブルから以下の3条件をすべて満たすユーザーを取得してください:
completedの注文が1件以上ある(EXISTS)
2024-05-16以降の注文が1件以上ある(EXISTS)
cancelledの注文が1件もない(NOT EXISTS)
取得列は user_id, name, plan、user_id昇順。

使用テーブル
▸ users
user_idnameplan
1田中 太郎premium
2佐藤 花子free
3鈴木 一郎premium
4山田 次郎standard
5伊藤 三郎free
▸ orders(ordered_at付き)
order_iduser_idamountstatusordered_at
10118000completed2024-04-10
102112000completed2024-05-20
10323500pending2024-05-10
10439500completed2024-05-15
10534000completed2024-06-01
10642000cancelled2024-05-08
10754500completed2024-04-20
10825500completed2024-06-10
期待出力
user_idnameplan
1田中 太郎premium
2佐藤 花子free
3鈴木 一郎premium
QUESTION 8

Top-N相関サブクエリ — カテゴリ別上位2件をサブクエリで取得する

相関SQTop-N抽出行番号付けランキング
前提知識

「カテゴリ別上位N件」の抽出は実務で非常に頻出です。相関サブクエリで「自分より高い行が何件あるか」を数えると、ROW_NUMBER()ウィンドウ関数なしでもTop-N抽出が実現できます。

SELECT p.*
FROM   products p
WHERE  (
  SELECT COUNT(*)
  FROM   products p2
  WHERE  p2.category = p.category   -- 同じカテゴリ内で
    AND  p2.price    >  p.price      -- 自分より高い商品が
) < 2;                               -- 2件未満 → 自分は上位2件
仕組み:「同カテゴリで自分より price が高い行が 0件 → 1位、1件 → 2位、2件以上 → 3位以下」。上位2件を取るには「自分より高い行が 2件未満(COUNT(*) < 2)」という条件になります。同価格の行が複数ある場合は全て「同順位」として返ります。
問題

products テーブルから、カテゴリ別に price 降順で上位2件の商品を取得してください。取得列は product_id, name, category, price、category 昇順・同一カテゴリ内は price 降順で並べてください。
サブクエリのみで実装し、ウィンドウ関数は使わないこと。

使用テーブル
▸ products
product_idnamecategoryprice
1プランAservice9800
2プランBservice4900
3プランCservice2500
4テンプレートXcontent5500
5テンプレートYcontent3800
6テンプレートZcontent1200
7APIアドオンoption3500
8サポート拡張option2200
9ストレージ追加option1500
期待出力
product_idnamecategoryprice
4テンプレートXcontent5500
5テンプレートYcontent3800
7APIアドオンoption3500
8サポート拡張option2200
1プランAservice9800
2プランBservice4900
QUESTION 9

多段CTE — WITH句を3段階で積み上げてコホート分析を実装する

多段CTEWITH句コホート分析ユーザー行動分析
前提知識

複数のCTE(WITH句)を連鎖させる多段CTEは、複雑な分析クエリを段階的に分解する実務の標準手法です。各CTEが前のCTEを参照できるため、「ステップ1の結果をステップ2で絞り込み、その結果をステップ3で集計」というパイプラインを直感的に記述できます。

WITH
  step1 AS (                         -- 第1段:基礎集計
    SELECT ... FROM source_table
  ),
  step2 AS (                         -- 第2段:step1を参照して絞り込み
    SELECT ... FROM step1 WHERE ...
  ),
  step3 AS (                         -- 第3段:step2をさらに加工
    SELECT ... FROM step2 JOIN ...
  )
SELECT * FROM step3;
多段CTEの価値:複雑なネストを「名前付きの段階」に分解することで、①各ステップを単独で実行・デバッグできる ②チームへの意図伝達が容易になる ③仕様変更時に影響範囲を特定しやすいというメリットがあります。
問題

以下の3ステップを多段CTEで実装してください:
user_stats:ユーザー別に completed注文の件数(order_cnt)と合計金額(total_spent)を集計
active_users:①から order_cnt が 2 以上のユーザーだけを抽出
③ 最終SELECT:②と usersテーブルを JOIN し、user_id, name, plan, order_cnt, total_spenttotal_spent 降順で返す

使用テーブル
▸ users
user_idnameplan
1田中 太郎premium
2佐藤 花子free
3鈴木 一郎premium
4山田 次郎standard
5伊藤 三郎free
▸ orders
order_iduser_idamountstatus
10118000completed
102112000completed
10323500pending
10439500completed
10534000completed
10642000cancelled
10754500completed
10816500completed
期待出力
user_idnameplanorder_cnttotal_spent
1田中 太郎premium326500
3鈴木 一郎premium213500
QUESTION 10

CTEスコアリング — 複数指標を独立集計してスコアを合算するユーザー評価クエリ

CTEスコアリングCASE+CTE複合応用リテンション分析
前提知識

実務では「合計金額・注文件数・活動頻度」など複数の指標を合算したスコアで顧客ランクを決めるケースがよくあります。各指標を独立したCTEで集計し、最終CTEでスコアを合算するパターンは保守性・可読性に優れた設計です。

WITH metric_a AS (                 -- ① 指標ごとに独立して集計する
  SELECT key_col, SUM(num_col) AS total
  FROM   table_a
  GROUP BY key_col
), metric_b AS (
  SELECT key_col, COUNT(*) AS cnt
  FROM   table_b
  GROUP BY key_col
), scored AS (                       -- ② 最終CTEでスコアを合算する
  SELECT a.key_col,
         CASE WHEN a.total >= 1000 THEN 3 ELSE 1 END
       + CASE WHEN b.cnt   >= 10   THEN 2 ELSE 0 END AS score
  FROM   metric_a a
  JOIN   metric_b b ON b.key_col = a.key_col
)
SELECT * FROM scored;
設計のポイント:スコアの計算ロジックを変えたいとき(例: 合計金額の重みを変える)は、該当CTEだけを修正すれば済みます。1つの巨大なSQLに全てのロジックを埋め込むより、変更箇所が明確で、チームレビューも容易です。
問題

以下の手順でユーザーの総合スコアを算出し、スコア降順・同スコアは user_id 昇順でランキングを返してください:
spend_score:ユーザー別の completed合計金額(total_spent)に基づき 10,000以上 → 3点 / 5,000以上 → 2点 / それ以下 → 1点(completedなしは0点)
freq_score:ユーザー別の completed注文件数(order_cnt)に基づき 3件以上 → 3点 / 2件以上 → 2点 / それ以下 → 1点(completedなしは0点)
③ 最終SELECT:全ユーザーを対象に user_id, name, spend_score, freq_score, total_score(合計) を返す

使用テーブル
▸ users
user_idnameplan
1田中 太郎premium
2佐藤 花子free
3鈴木 一郎premium
4山田 次郎standard
5伊藤 三郎free
▸ orders
order_iduser_idamountstatus
10118000completed
102112000completed
10323500pending
10439500completed
10534000completed
10642000cancelled
10754500completed
10816500completed
集計対象と比較単位を整理してから、サブクエリの返す値を決めてください。
期待出力
user_idnamespend_scorefreq_scoretotal_score
1田中 太郎336
3鈴木 一郎325
5伊藤 三郎112
2佐藤 花子000
4山田 次郎000