相関分析 — CORR でピアソン相関係数を算出し変数間の線形関係を定量化する
CORR(Y, X) は2変数間のピアソン積率相関係数を返す集計関数です。値の範囲は −1 〜 +1 で、+1 に近いほど強い正の線形相関、−1 に近いほど強い負の線形相関、0 は線形相関なしを意味します。
-- 書式: CORR(Y, X) ← 相関は対称なので引数の順序は結果に影響しない CORR(revenue, ad_cost) -- 計算式(ピアソン積率相関係数): -- r = Σ(xi−x̄)(yi−ȳ) / √[ Σ(xi−x̄)² × Σ(yi−ȳ)² ] -- 分子: 共分散の和(正→正の相関, 負→負の相関) -- 分母: 両変数の標準偏差の積(スケール正規化 → −1〜+1 に収まる)
GROUP BY category でカテゴリ別の相関を一括算出できます。一方 OVER 句(ウィンドウ関数としての使用)は非対応です。また CORR は double precision を返すため、ROUND と組み合わせる際は ::numeric キャストが必要です。ad_spend テーブルを使い、広告費(ad_cost)と売上(revenue)のピアソン相関係数と計算対象の行数(n)を1行で算出してください。取得列は corr_coef(小数第4位まで), n。
| week | ad_cost | revenue |
|---|---|---|
| 1 | 100 | 280 |
| 2 | 200 | 650 |
| 3 | 300 | 780 |
| 4 | 400 | 1100 |
| 5 | 500 | 1250 |
| 6 | 600 | 1640 |
| corr_coef | n |
|---|---|
| 0.9914 | 6 |
SELECT ROUND(CORR(revenue, ad_cost)::numeric, 4) AS corr_coef, -- ピアソン相関係数(−1〜+1) COUNT(*) AS n -- 計算対象の行数 FROM ad_spend; /* 実行順序(SQLの論理的な評価順): 1. FROM ad_spend → 6行読込 2. CORR(revenue, ad_cost) → 相関係数を計算 3. COUNT(*) → 件数を集計 4. ROUND(...::numeric, 4) → 小数4桁に丸め 5. SELECT → 1行出力 */
LEGEND
① FROM ad_spend — 広告費と売上の週次データ6行
FROM ad_spendad_spend テーブルから全6行を読み込みます。ad_cost(説明変数 X)と revenue(目的変数 Y)の線形関係を CORR 関数で定量化します。NULL を含む行は自動除外されます。| week | ▸ ad_cost | ▸ revenue |
|---|---|---|
| 1 | 100 | 280 |
| 2 | 200 | 650 |
| 3 | 300 | 780 |
| 4 | 400 | 1100 |
| 5 | 500 | 1250 |
| 6 | 600 | 1640 |
CORR(revenue, ad_cost) に GROUP BY channel を加えると、SNS・TV・検索広告ごとの相関係数を1クエリで比較できます。相関係数がチャネルで大きく異なる場合は広告の効き方がジャンルに依存しているというインサイトになります。LAG(ad_cost,1) OVER (ORDER BY week) を使って1週間ラグの相関も算出し、広告の翌週への遅延効果も検出します。線形回帰 — REGR_SLOPE / REGR_INTERCEPT / REGR_R2 で回帰モデルを構築し売上を予測する
PostgreSQL の REGR_* 関数群は、最小二乗法による単回帰分析をSQLで完結させます。目的変数 Y と説明変数 X の関係を Y = slope × X + intercept でモデル化します。
REGR_SLOPE(Y, X) -- 傾き: Σ(xi−x̄)(yi−ȳ) / Σ(xi−x̄)² REGR_INTERCEPT(Y, X) -- Y切片: ȳ − slope × x̄ REGR_R2(Y, X) -- 決定係数 R²: CORR(Y,X)² — 0〜1でモデルの当てはまりを示す REGR_COUNT(Y, X) -- NULL を除いた有効サンプル数 -- 予測値の算出(CTE の slope・intercept を再利用): (slope * 700 + intercept)::numeric
同じ ad_spend テーブルを使い、傾き(slope)・切片(intercept)・決定係数(r_squared)と、ad_cost = 700 のときの予測売上(predicted_700)を1行で算出してください。slope は小数第4位、intercept は小数第2位、r_squared は小数第4位、predicted_700 は整数丸め。
| week | ad_cost | revenue |
|---|---|---|
| 1 | 100 | 280 |
| 2 | 200 | 650 |
| 3 | 300 | 780 |
| 4 | 400 | 1100 |
| 5 | 500 | 1250 |
| 6 | 600 | 1640 |
| slope | intercept | r_squared | predicted_700 |
|---|---|---|---|
| 2.5486 | 58.00 | 0.9829 | 1842 |
WITH stats AS ( -- REGR_* 関数で回帰パラメータを一括算出(CTE で slope・intercept を再利用) SELECT REGR_SLOPE(revenue, ad_cost) AS slope, REGR_INTERCEPT(revenue, ad_cost) AS intercept, REGR_R2(revenue, ad_cost) AS r_squared FROM ad_spend ) SELECT ROUND(slope::numeric, 4) AS slope, ROUND(intercept::numeric, 2) AS intercept, ROUND(r_squared::numeric, 4) AS r_squared, ROUND((slope * 700 + intercept)::numeric, 0) AS predicted_700 -- 回帰式でad_cost=700の予測値 FROM stats; /* 実行順序(SQLの論理的な評価順): 1. CTE stats — FROM ad_spend → 6行読込 2. REGR_SLOPE(...) → 回帰の傾きを計算 3. REGR_INTERCEPT(...) → 切片を計算 4. REGR_R2(...) → 決定係数を計算 5. FROM stats → CTE の1行を参照 6. predicted_700 → 予測値を計算 7. 各 ROUND 適用 → 丸め 8. SELECT → 1行出力 */
LEGEND
① FROM ad_spend — 同じデータで回帰モデルを構築
FROM ad_spendQ6で CORR=0.9914 という強い正の相関を確認済み。このデータで Y=slope×X+intercept の回帰式を構築し、広告費から売上を予測するモデルを作ります。x̄=350, ȳ=950。| week | ▸ ad_cost (X) | ▸ revenue (Y) |
|---|---|---|
| 1 | 100 | 280 |
| 2 | 200 | 650 |
| 3 | 300 | 780 |
| 4 | 400 | 1100 |
| 5 | 500 | 1250 |
| 6 | 600 | 1640 |
REGR_COUNT(Y, X) は Y・X どちらかが NULL の行を除いた有効件数を返します。COUNT(*) と REGR_COUNT の差が大きい場合は NULL が多く、モデルの信頼性が低下している可能性があります。GROUP BY quarter で四半期別モデルを並列構築すると、季節性を持つカテゴリで「夏モデル」「冬モデル」を使い分ける高度な予測も実現できます。ランキング分析 — RANK / DENSE_RANK / ROW_NUMBER でタイ(同点)処理の違いを理解する
ウィンドウ関数によるランキングには3種類あり、同点(タイ)のときの挙動が異なります。PARTITION BY でグループごとに独立したランキングを付与できます。
RANK() OVER (PARTITION BY region ORDER BY sales DESC) -- タイ行に同じ順位 / 次の順位をスキップ: 1,1,3(2をスキップ) DENSE_RANK() OVER (PARTITION BY region ORDER BY sales DESC) -- タイ行に同じ順位 / 次の順位をスキップしない: 1,1,2(連続) ROW_NUMBER() OVER (PARTITION BY region ORDER BY sales DESC) -- 常に連番 / タイ行でも異なる番号(タイ時の順序は非決定的): 1,2,3
sales_rep テーブルを使い、地域(region)ごとに売上(sales_amount)の降順ランキングを RANK・DENSE_RANK・ROW_NUMBER の3種類で付与してください。取得列は rep_id, region, sales_amount, rank_val, dense_rank_val, row_num、region, rank_val, rep_id の順で並べてください。
| rep_id | region | sales_amount |
|---|---|---|
| 1 | East | 8500 |
| 2 | East | 6200 |
| 3 | East | 8500 |
| 4 | West | 9100 |
| 5 | West | 7800 |
| 6 | West | 5500 |
| 7 | North | 7200 |
| 8 | North | 7200 |
| 9 | North | 4300 |
| rep_id | region | sales_amount | rank_val | dense_rank_val | row_num |
|---|---|---|---|---|---|
| 1 | East | 8500 | 1 | 1 | 1 |
| 3 | East | 8500 | 1 | 1 | 2 |
| 2 | East | 6200 | 3 | 2 | 3 |
| 7 | North | 7200 | 1 | 1 | 1 |
| 8 | North | 7200 | 1 | 1 | 2 |
| 9 | North | 4300 | 3 | 2 | 3 |
| 4 | West | 9100 | 1 | 1 | 1 |
| 5 | West | 7800 | 2 | 2 | 2 |
| 6 | West | 5500 | 3 | 3 | 3 |
SELECT rep_id, region, sales_amount, RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) AS rank_val, DENSE_RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) AS dense_rank_val, ROW_NUMBER() OVER (PARTITION BY region ORDER BY sales_amount DESC, rep_id ASC) AS row_num -- タイは rep_id で安定化 FROM sales_rep ORDER BY region, rank_val, rep_id; /* 実行順序(SQLの論理的な評価順): 1. FROM sales_rep → 9行読込 2. PARTITION BY region → 3グループに分割 3. ORDER BY sales_amount DESC → 各グループ内ソート 4. RANK() → タイは同順位・次をスキップ 5. DENSE_RANK() → タイは同順位・スキップなし 6. ROW_NUMBER() → 常に連番 7. ORDER BY region, rank_val, rep_id → 最終ソート 8. SELECT → 9行出力 */
LEGEND
① FROM sales_rep — 3地域・9名の担当者別売上
FROM sales_repEast・West・North の3地域、各3名の担当者データを読み込みます。East(rep1,3: 8500同点)とNorth(rep7,8: 7200同点)にタイが存在し、3つのランキング関数の挙動の違いが明確に現れます。| rep_id | ▸ region | ▸ sales_amount |
|---|---|---|
| 1 | East | 8500 |
| 2 | East | 6200 |
| 3 | East | 8500 |
| 4 | West | 9100 |
| 5 | West | 7800 |
| 6 | West | 5500 |
| 7 | North | 7200 |
| 8 | North | 7200 |
| 9 | North | 4300 |
WHERE dense_rank_val <= N)。「ページネーション・重複排除・N件固定取得」→ ROW_NUMBER(タイブレーカー追加で非決定性を排除)。WHERE row_num = 1 でタイの行を絞ると1人しか取得されず同点者が漏れます。同率1位全員を取得する場合は WHERE rank_val = 1 を使ってください。WHERE rank_val <= 3 にすると取得件数が予測しにくくなります(同率3位が複数なら3件超)。件数を N 件に確定したい場合は ROW_NUMBER(タイブレーカー付き)を使ってください。RANK() OVER (ORDER BY sales_amount DESC)(PARTITION BY なし)で全社ランキングを取りつつ、同一クエリで PARTITION BY region のランキングも並べれば「全社3位かつ地域1位」のような複合KPIも実現できます。分位数分割 — NTILE でユーザーを購買額四分位にセグメント化する
NTILE(n) は行を n 等分し、各行にバケット番号(1〜n)を割り当てるウィンドウ関数です。RFM 分析・ユーザーセグメント分類・パーセンタイルグループ化に活用されます。
NTILE(4) OVER (ORDER BY total_spent DESC) -- 8行を4分割 → 各グループ2行ずつ -- quartile=1: 購買額上位25%(VIP) -- quartile=4: 購買額下位25%(ライト層) -- 行数が n で割り切れない場合: -- 余り分を上位のバケットから1行ずつ追加(例: 9行÷4 → バケット1だけ3行)
user_spend テーブルの total_spent を降順で4分割し、各ユーザーの分位番号(quartile: 1〜4)とセグメント名(segment: VIP / Heavy / Middle / Light)を付与してください。CTE でバケットを付与してから外側クエリでセグメント名を生成し、quartile, total_spent DESC の順で返してください。
| user_id | total_spent |
|---|---|
| 1 | 250 |
| 2 | 1800 |
| 3 | 420 |
| 4 | 3200 |
| 5 | 890 |
| 6 | 5600 |
| 7 | 150 |
| 8 | 2100 |
| user_id | total_spent | quartile | segment |
|---|---|---|---|
| 6 | 5600 | 1 | VIP |
| 4 | 3200 | 1 | VIP |
| 8 | 2100 | 2 | Heavy |
| 2 | 1800 | 2 | Heavy |
| 5 | 890 | 3 | Middle |
| 3 | 420 | 3 | Middle |
| 1 | 250 | 4 | Light |
| 7 | 150 | 4 | Light |
WITH segmented AS ( -- NTILE(4) で降順購買額を4等分し、バケット番号(1=上位25%)を付与 SELECT user_id, total_spent, NTILE(4) OVER (ORDER BY total_spent DESC) AS quartile FROM user_spend ) SELECT user_id, total_spent, quartile, CASE quartile WHEN 1 THEN 'VIP' WHEN 2 THEN 'Heavy' WHEN 3 THEN 'Middle' WHEN 4 THEN 'Light' END AS segment FROM segmented ORDER BY quartile, total_spent DESC; /* 実行順序(SQLの論理的な評価順): 1. CTE segmented — FROM user_spend → 8行読込 2. NTILE(4) OVER (...) → 4分位に割り当て 3. 外側SELECT FROM segmented → CTE を参照 4. CASE quartile → 番号をビジネス用語に変換 5. ORDER BY quartile, total_spent DESC → 最終ソート 6. SELECT → 8行出力 */
LEGEND
① FROM user_spend — 8ユーザーの累計購買額(順序バラバラ)
FROM user_spend8ユーザーの total_spent を読み込みます。このまま順序はバラバラです。NTILE(4) で購買額の高い順に4等分し、VIP・Heavy・Middle・Light の4セグメントに分類します。| user_id | ▸ total_spent |
|---|---|
| 1 | 250 |
| 2 | 1800 |
| 3 | 420 |
| 4 | 3200 |
| 5 | 890 |
| 6 | 5600 |
| 7 | 150 |
| 8 | 2100 |
GROUP BY quartile で各層の AVG(total_spent) や COUNT を算出し、「VIP層の平均購買額はLight層の何倍か」を把握するのが実務の分析フローです。NTILE(5) OVER (ORDER BY last_order_date DESC) AS r_score(最近注文した人ほどスコアが高い)のように3つのウィンドウ関数を並べ、CTE で集約します。このスコアをセグメント名に変換し、CRMツールや広告配信プラットフォームに連携するのが実務的なユーザーセグメント活用の完成形です。前期比分析 — LAG / LEAD で月次成長率(MoM)を1クエリで算出する
LAG(col, offset, default) は現在行より offset 行前の値を返すウィンドウ関数です。前月比・前年同期比などの時系列比較に不可欠で、自己 JOIN よりシンプルに記述できます。
LAG(col, 1) OVER (ORDER BY month) -- 1行前の値(前月) LAG(col, 12) OVER (ORDER BY month) -- 12行前の値(前年同月) LEAD(col, 1) OVER (ORDER BY month) -- 1行後の値(翌月) -- 前月比(MoM)の計算: -- (当月revenue − 前月revenue) / 前月revenue × 100 -- ※ 最初の行は前月がないため NULL になる(正常な動作)
prev_revenue = LAG(revenue, 1) OVER (...) を確定させ、外側クエリで (revenue - prev_revenue) / prev_revenue と書くパターンが可読性と保守性に優れます。monthly_revenue テーブルを使い、各月の売上・前月売上(prev_revenue)・前月比成長率(mom_growth_pct: 小数第2位)を算出してください。CTE でまず LAG による前月値を付与し、外側クエリで成長率を計算してください。初月は prev_revenue / mom_growth_pct ともに NULL で構いません。
| month | revenue |
|---|---|
| 2024-01 | 1200000 |
| 2024-02 | 1350000 |
| 2024-03 | 1180000 |
| 2024-04 | 1520000 |
| 2024-05 | 1680000 |
| 2024-06 | 1450000 |
| month | revenue | prev_revenue | mom_growth_pct |
|---|---|---|---|
| 2024-01 | 1200000 | NULL | NULL |
| 2024-02 | 1350000 | 1200000 | 12.50 |
| 2024-03 | 1180000 | 1350000 | -12.59 |
| 2024-04 | 1520000 | 1180000 | 28.81 |
| 2024-05 | 1680000 | 1520000 | 10.53 |
| 2024-06 | 1450000 | 1680000 | -13.69 |
WITH lagged AS ( -- LAG で前月売上を同じ行に並べる(CTE で先に確定させ外側で再利用) SELECT month, revenue, LAG(revenue, 1) OVER (ORDER BY month) AS prev_revenue FROM monthly_revenue ) SELECT month, revenue, prev_revenue, ROUND( (revenue - prev_revenue)::numeric / prev_revenue * 100, 2) AS mom_growth_pct -- prev_revenue=NULL の場合も NULL が自動伝播 FROM lagged ORDER BY month; /* 実行順序(SQLの論理的な評価順): 1. CTE lagged — FROM monthly_revenue → 6行読込 2. LAG(revenue, 1) OVER (...) → 前月の revenue を付与 3. FROM lagged → CTE を参照 4. (revenue - prev) / prev * 100 → 前月比成長率を計算 5. ROUND(..., 2) → 小数第2位で丸め 6. ORDER BY month → 時系列昇順 7. SELECT → 6行出力 */
LEGEND
① FROM monthly_revenue — 6ヶ月の月次売上データ
FROM monthly_revenue2024年1〜6月の月次売上データ6行を読み込みます。LAG 関数で各月の「前月の revenue」を同じ行に並べることで、自己 JOIN なしで前月比成長率を1クエリで算出します。| month | ▸ revenue |
|---|---|
| 2024-01 | 1,200,000 |
| 2024-02 | 1,350,000 |
| 2024-03 | 1,180,000 |
| 2024-04 | 1,520,000 |
| 2024-05 | 1,680,000 |
| 2024-06 | 1,450,000 |
COALESCE(mom_growth_pct, 0)、表示を「−」にしたい場合は COALESCE(CAST(mom_growth_pct AS TEXT), '−') のように COALESCE で後処理します。LAG(revenue, 1, 0) のように第3引数を指定すると、前行がない場合に NULL ではなく指定したデフォルト値(この例では 0)を返します。ただし 0 除算が発生するため成長率の計算には注意が必要です。目的に応じて NULL 伝播とデフォルト値指定を使い分けてください。LAG(revenue, 1) OVER () と ORDER BY を省略すると、行の参照順序が不定になり前月比が毎回変わる可能性があります。LAG/LEAD には必ず ORDER BY を指定して時系列の順序を確定させてください。/ prev_revenue はゼロ除算エラーになります。NULLIF(prev_revenue, 0) でゼロを NULL に変換してから割り算し、ゼロ除算を防ぐのが実務の定番パターンです:(revenue - prev_revenue)::numeric / NULLIF(prev_revenue, 0) * 100。PARTITION BY product_category を加えるとカテゴリ別の時系列比較も同一クエリで実現でき、BIツールへのデータ供給クエリとして直接活用できます。