本文へ移動
売上・LTV分析SQL 更新日: 2026年5月2日 約19分で読めます

LTVをSQLで計算する方法|顧客生涯価値を正確に出す実務クエリ


「LTVが高い顧客」を、勘ではなく数字で語れるか

「LTV(Life Time Value:顧客生涯価値)を上げましょう」

マーケティングの会議でこの言葉が出るたびに、筆者は密かに問いかけている。「では今のLTVはいくらですか?」と。

明確に答えられる人は、驚くほど少ない。

LTVとは一言で言えば「1人の顧客が生涯を通じて自社にもたらす利益の総額」だ。この数字を把握しておくと、次のような判断が定量的にできるようになる。

  • 広告の顧客獲得単価(CPA)はいくらまで許容できるか
  • どの顧客セグメントに優先的にリソースを投下すべきか
  • 解約・離脱を1件防ぐことで回収できる金額はいくらか

今回はLTVの定義を丁寧に整理したうえで、累計購買額・平均注文頻度・予測LTVの3つをSQLで計算するクエリを段階的に作っていく。


LTVの定義を整理する ― 「どのLTV」を計算するのか

実はLTVには複数の定義があり、何を計算するかを最初に決めないとチーム内で議論がかみ合わなくなる。代表的な3つを整理しておこう。

定義1:累計購買額LTV(実績ベース)

累計LTV = Σ(購買金額)

これまでに実際に使ってくれた金額の合計だ。計算が単純で誰でも理解しやすい。「この顧客は入会からこれまでに合計〇〇円使っている」という実績の話だ。

定義2:平均LTV(コホートベース)

平均LTV = 平均注文金額 × 年間購買回数 × 平均継続年数

「平均的な顧客が生涯でいくら使うか」の推計値だ。事業全体を俯瞰するときに使う。

定義3:予測LTV(将来推計)

予測LTV = 平均注文金額 × 購買頻度 × 予測残存期間

「この顧客はこれから先、何年間購買を続けてくれるか」を組み込んだ将来価値だ。CRM投資の判断に最も適している。

今回はこの3つを順番にSQLで計算していく。


使用するテーブル

前回のRFM分析と同じ orders テーブルを使う。

-- orders テーブル
-- order_id      : 注文ID
-- customer_id   : 顧客ID
-- order_date    : 注文日(DATE型)
-- total_amount  : 注文金額(税抜)
-- status        : 'completed' / 'cancelled' など

加えて、顧客の初回登録日を持つ customers テーブルも使う。

-- customers テーブル
-- customer_id   : 顧客ID(主キー)
-- registered_at : 会員登録日(DATE型)
-- channel       : 初回流入チャネル('organic' / 'paid' / 'referral' など)

STEP 1 ― 累計購買額LTVを計算する

まずは最もシンプルな「実績ベースのLTV」から。

顧客ごとの累計購買金額・購買回数・平均注文金額・初回購買日・最終購買日を一度に出す。

-- STEP1: 顧客別の累計購買実績
WITH customer_orders AS (
    SELECT
        customer_id,
        COUNT(order_id)             AS total_orders,
        SUM(total_amount)           AS total_spent,
        ROUND(AVG(total_amount), 0) AS avg_order_value,
        MIN(order_date)             AS first_order_date,
        MAX(order_date)             AS last_order_date
    FROM orders
    WHERE status = 'completed'
    GROUP BY customer_id
)
SELECT
    co.customer_id,
    c.registered_at,
    c.channel,
    co.total_orders,
    co.total_spent,
    co.avg_order_value,
    co.first_order_date,
    co.last_order_date,

    -- 初回購買から最終購買までの活動期間(日数)
    DATE_DIFF('day', co.first_order_date, co.last_order_date)  AS active_days
FROM customer_orders  co
JOIN customers        c  ON co.customer_id = c.customer_id
ORDER BY co.total_spent DESC;

出力イメージ

customer_idregistered_atchanneltotal_orderstotal_spentavg_order_valueactive_days
C0042021-03-12paid18284,60015,8111,024
C0012022-08-05organic898,40012,300487
C0122023-01-20referral572,00014,400312

この段階ですでに「3年間で18回購買、累計28万円のC004さん」のような顧客像が見えてくる。


STEP 2 ― 年間購買頻度と在籍期間を計算する

予測LTVを計算するためには、「1年間に平均何回買うか」と「何年間お付き合いが続くか」が必要になる。

-- STEP2: 年間購買頻度と在籍期間を追加

WITH customer_orders AS (
    SELECT
        customer_id,
        COUNT(order_id)             AS total_orders,
        SUM(total_amount)           AS total_spent,
        ROUND(AVG(total_amount), 0) AS avg_order_value,
        MIN(order_date)             AS first_order_date,
        MAX(order_date)             AS last_order_date
    FROM orders
    WHERE status = 'completed'
    GROUP BY customer_id
),
customer_metrics AS (
    SELECT
        co.customer_id,
        c.registered_at,
        c.channel,
        co.total_orders,
        co.total_spent,
        co.avg_order_value,
        co.first_order_date,
        co.last_order_date,

        -- 在籍日数(登録日〜基準日)
        DATE_DIFF('day', c.registered_at, DATE '2024-12-31')  AS tenure_days,

        -- 年間購買回数 = 総購買回数 ÷ 在籍年数
        -- 在籍日数が0にならないよう NULLIF で保護する
        ROUND(
            co.total_orders * 365.0
            / NULLIF(DATE_DIFF('day', c.registered_at, DATE '2024-12-31'), 0)
        , 2)  AS annual_purchase_frequency
    FROM customer_orders  co
    JOIN customers        c  ON co.customer_id = c.customer_id
)
SELECT *
FROM customer_metrics
ORDER BY total_spent DESC;

NULLIF とは?
NULLIF(a, b) は「a が b に等しければ NULL を返す」関数だ。ここでは在籍日数が0になったとき(登録当日に集計した場合など)にゼロ除算エラーが起きないよう保護するために使っている。NULLによる除算はエラーではなくNULLを返すため、安全に処理できる。


STEP 3 ― 予測LTVを計算する

ここが今回の本丸だ。

予測LTVの計算式は下記の通り。

予測LTV = 平均注文金額 × 年間購買頻度 × 予測残存期間(年)

「予測残存期間」は本来であればチャーンレートから逆算するが、今回はシンプルに「業界標準の3年間」を仮定する。自社のデータが蓄積されてきたら、実際の継続率から算出した数値に置き換えてほしい。

-- STEP3: 予測LTV算出(完成版)

WITH customer_orders AS (
    SELECT
        customer_id,
        COUNT(order_id)             AS total_orders,
        SUM(total_amount)           AS total_spent,
        ROUND(AVG(total_amount), 0) AS avg_order_value,
        MIN(order_date)             AS first_order_date,
        MAX(order_date)             AS last_order_date
    FROM orders
    WHERE status = 'completed'
    GROUP BY customer_id
),
customer_metrics AS (
    SELECT
        co.customer_id,
        c.registered_at,
        c.channel,
        co.total_orders,
        co.total_spent,
        co.avg_order_value,
        co.first_order_date,
        co.last_order_date,
        DATE_DIFF('day', c.registered_at, DATE '2024-12-31')  AS tenure_days,
        ROUND(
            co.total_orders * 365.0
            / NULLIF(DATE_DIFF('day', c.registered_at, DATE '2024-12-31'), 0)
        , 2)  AS annual_purchase_frequency
    FROM customer_orders  co
    JOIN customers        c  ON co.customer_id = c.customer_id
),
ltv_calculated AS (
    SELECT
        customer_id,
        registered_at,
        channel,
        total_orders,
        total_spent,
        avg_order_value,
        annual_purchase_frequency,
        tenure_days,

        -- 予測LTV = 平均注文金額 × 年間購買頻度 × 残存期間(3年)
        ROUND(
            avg_order_value * annual_purchase_frequency * 3
        , 0)  AS predicted_ltv_3yr,

        -- LTVランク(予測LTV上位20%をHighと定義)
        CASE
            WHEN NTILE(5) OVER (ORDER BY avg_order_value * annual_purchase_frequency ASC) = 5
                THEN 'High'
            WHEN NTILE(5) OVER (ORDER BY avg_order_value * annual_purchase_frequency ASC) >= 3
                THEN 'Mid'
            ELSE
                'Low'
        END  AS ltv_rank
    FROM customer_metrics
    WHERE annual_purchase_frequency IS NOT NULL
)
SELECT *
FROM ltv_calculated
ORDER BY predicted_ltv_3yr DESC;

出力イメージ

customer_idchanneltotal_spentavg_order_valueannual_purchase_frequencypredicted_ltv_3yrltv_rank
C004paid284,60015,8116.40303,571High
C001organic98,40012,3005.99221,031High
C012referral72,00014,4005.85252,720High
C023paid45,0009,0001.8249,140Mid

STEP 4 ― チャネル別・LTVランク別のサマリを出す

最終的に「どの流入チャネルがLTVの高い顧客を連れてくるか」を把握することで、広告投資の最適化ができる。

-- STEP4: チャネル別LTVサマリ

WITH customer_orders AS (
    SELECT
        customer_id,
        COUNT(order_id)             AS total_orders,
        SUM(total_amount)           AS total_spent,
        ROUND(AVG(total_amount), 0) AS avg_order_value
    FROM orders
    WHERE status = 'completed'
    GROUP BY customer_id
),
customer_metrics AS (
    SELECT
        co.customer_id,
        c.channel,
        co.avg_order_value,
        ROUND(
            co.total_orders * 365.0
            / NULLIF(DATE_DIFF('day', c.registered_at, DATE '2024-12-31'), 0)
        , 2)  AS annual_purchase_frequency
    FROM customer_orders  co
    JOIN customers        c  ON co.customer_id = c.customer_id
),
ltv_calculated AS (
    SELECT
        customer_id,
        channel,
        ROUND(avg_order_value * annual_purchase_frequency * 3, 0)  AS predicted_ltv_3yr
    FROM customer_metrics
    WHERE annual_purchase_frequency IS NOT NULL
)
SELECT
    channel,
    COUNT(customer_id)                  AS customer_count,
    ROUND(AVG(predicted_ltv_3yr), 0)    AS avg_ltv,
    ROUND(MEDIAN(predicted_ltv_3yr), 0) AS median_ltv,
    MAX(predicted_ltv_3yr)              AS max_ltv,
    SUM(predicted_ltv_3yr)              AS total_ltv
FROM ltv_calculated
GROUP BY channel
ORDER BY avg_ltv DESC;

出力イメージ

channelcustomer_countavg_ltvmedian_ltvmax_ltvtotal_ltv
referral412198,400142,000680,00081,740,800
organic1,284156,20098,000950,000200,560,800
paid2,34189,30054,000720,000209,051,300

このサマリが示すのは、たとえばこういうことだ。

  • referral(紹介)チャネルは顧客数こそ少ないが、平均LTVは最も高い。紹介プログラムへの投資対効果が高い可能性がある
  • paid(有料広告)は顧客数は最多だが、平均LTVは最も低い。CPAと平均LTVを比較したとき、広告投資が本当に見合っているかを検証すべきだ
  • organic(自然流入)は平均LTVと顧客数のバランスが良い。SEOへの継続投資が長期的に最も安定したリターンをもたらしている可能性がある

実務での運用ヒント

① 平均よりも中央値(MEDIAN)を見る

LTVの分布は必ず右に偏る(一部のヘビーユーザーが平均を引き上げる)。意思決定には平均よりも中央値を参考にするほうが実態に即していることが多い。Treasure DataのPrestoでは APPROX_PERCENTILE(column, 0.5) で中央値に近い値が得られる。

② 在籍期間が極端に短い顧客は除外を検討する

登録から30日未満の顧客は年間購買頻度の推計が不安定になりやすい。WHERE tenure_days >= 90 などのフィルタを追加すると精度が上がる。

③ 粗利ベースのLTVを最終目標にする

今回は売上ベースで計算したが、本来のLTVは「利益」ベースで計算するのが正しい。商品ごとの原価データが揃ってきたら、total_amount * 粗利率 で計算するクエリにアップグレードしたい。


まとめ

今回のクエリの流れを振り返る。

  1. GROUP BY で顧客別の購買実績(累計金額・購買回数・平均注文金額)を集計
  2. customers テーブルとJOINして在籍期間を計算し、年間購買頻度を導出
  3. 平均注文金額 × 年間購買頻度 × 残存期間 で予測LTVを算出
  4. NTILE() でLTVランクをつけ、チャネル別サマリで投資判断の材料にする

前回のRFMが「今この顧客はどんな状態か」を教えてくれるなら、LTVは「この顧客は将来どれくらいの価値をもたらすか」を教えてくれる。2つを組み合わせることで、「RFMスコアは高いがLTVは低い顧客(買い物回数は多いが単価が低い)」や「RFMスコアは低いがLTVポテンシャルが高い顧客(離脱中だが過去単価が高い)」といった、より精緻なセグメントが生まれる。

次のアクション

SQLやデータ活用を、手元で試しながら理解する

記事で読んだ考え方を、SQL練習場や関連カテゴリの記事でさらに深掘りできます。相談やご依頼もお問い合わせページから受け付けています。

SQL練習場で試す お問い合わせ

この記事を書いた人:martechfarm

Treasure Data Top Lapidarist Award受賞。

SQL / Digdag / Python / CDP設計 / CRM設計を横断し、企業のデータ活用を支援。

実績・支援内容を見る →

MarTech Farmをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む