はじめに:クーポンは本来買う客にも割引を配っている

クーポンを配ると、売上は伸びます。割引があれば買い控えていた人が動きますし、財布のひもも緩みます。だから「クーポン配布期間の売上が増えた」という事実だけを見て、施策が成功したと判断してしまいがちです。

しかし、クーポンには見落とされやすい側面があります。それは、割引がなくても買ってくれたはずの客にまで、値引きを配ってしまっているという点です。

たとえば、もともと今日カートに入れていた商品があったとします。その人がレジ前で「クーポンが使える」と気づいて適用すれば、店側は何もせずとも値引き分だけ利益を削ったことになります。この客は割引がなくても買ったはずなのに、クーポンによって単に粗利が目減りしただけです。

このように、施策が自分自身の通常売上を食ってしまう現象を、カニバリゼーション(自己食い、共食い)と呼びます。クーポン施策の効果測定で本当に知りたいのは「クーポン経由でいくら売れたか」ではなく、クーポンがなかった場合と比べて、どれだけ売上を上乗せできたかです。

この記事では、GA4のデータをBigQueryに集約し、クーポン利用者と非利用者を比較しながら、クーポン施策のカニバリゼーションを検証する方法を解説します。SQLのサンプルも載せるので、自社のデータに当てはめながら読み進めてみてください。

なお、本記事はGA4からBigQueryへのエクスポートが設定済みで、events_YYYYMMDD テーブルが揃っていること、そして購入イベントにクーポンコードや割引額の情報が含まれていることを前提にしています。エクスポートの設定や受注データとの突合、before/after比較の基本的な考え方については、以下の記事も合わせて参考にしてください。

カニバリゼーションの考え方

検証SQLを書く前に、「クーポンのカニバリゼーションとは何を指すのか」を整理しておきます。ここがあいまいだと、SQLで数字を出しても解釈を誤ります。

クーポンを使って購入した人は、大きく2つのタイプに分かれます。

  • 本来は買わなかった人:クーポンの割引があったからこそ、購入を決めた人。この人の売上はクーポンが生み出した上乗せ(インクリメンタル)です。
  • クーポンがなくても買った人:もともと購入する気だった人。この人にとってクーポンは購入のきっかけではなく、単なる値引きです。

カニバリゼーションとは、後者のタイプに配ってしまった割引のことを指します。割引額そのものは、店側にとって純粋なコストです。クーポンの真の効果を測るには、利用者全体の売上から「もともと買ったはずの人の分」を差し引いて考える必要があります。

ここで難しいのは、購入した一人ひとりについて「この人はクーポンがなくても買ったか」を直接知ることはできない、という点です。本人の頭の中はデータに残りません。

そこで使うのが比較です。クーポンを使えた集団と、同じ条件で使わなかった(あるいは配られなかった)集団を並べ、その差を見ます。差が小さければ、クーポンはほとんど自己食いしている可能性が高い。差が大きければ、クーポンが新たな購入を生んでいる可能性が高い。直接は測れないものを、集団同士の差として近似するわけです。

検証その1:利用者と非利用者を並べて見る

まずは、同じ期間内でクーポンを使った人と使わなかった人を並べ、購入単価や購入率の傾向をつかみます。これは厳密な因果の証明ではなく、最初の感触を得るためのステップです。

次のSQLは、クーポン配布期間中の購入イベントを対象に、クーポン利用の有無で売上・件数・客単価を集計します。GA4の purchase イベントには、ecommerce.purchase_revenue などの値が入っている前提です。クーポンコードはアイテムレベルの coupon か、event_params に独自に送っている値を想定しています。

-- クーポン配布期間内の購入を、利用有無で比較する
WITH purchases AS (
  SELECT
    user_pseudo_id,
    (SELECT ep.value.string_value
       FROM UNNEST(event_params) ep
      WHERE ep.key = 'transaction_id') AS transaction_id,
    -- クーポンコードを取り出す(独自パラメータで送っている場合)
    (SELECT ep.value.string_value
       FROM UNNEST(event_params) ep
      WHERE ep.key = 'coupon') AS coupon_code,
    ecommerce.purchase_revenue AS revenue
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260701' AND '20260731'
    AND event_name = 'purchase'
)

SELECT
  IF(coupon_code IS NOT NULL AND coupon_code != '', 'クーポン利用', 'クーポンなし') AS segment,
  COUNT(DISTINCT transaction_id) AS orders,
  COUNT(DISTINCT user_pseudo_id) AS buyers,
  ROUND(SUM(revenue)) AS total_revenue,
  ROUND(SUM(revenue) / COUNT(DISTINCT transaction_id)) AS avg_order_value
FROM purchases
GROUP BY segment
ORDER BY segment;

この結果で注目するのは、クーポン利用セグメントの客単価です。

もしクーポン利用者の客単価が、クーポンなしの客単価から割引額をほぼそのまま引いただけの水準になっていたら、要注意です。それは「いつもどおり買い物をした人が、クーポン分だけ安く買った」状態に近く、カニバリゼーションが起きているサインです。

逆に、クーポン利用者がまとめ買いをして割引前の単価がむしろ高い、といった傾向が見えれば、クーポンが購買行動を変えている可能性があります。

ただし、この比較だけで結論を出すのは早計です。クーポンを使う人と使わない人は、そもそも性質が違うかもしれないからです。次のステップで時間軸を加えます。

検証その2:before/afterで全体の動きを見る

クーポン利用者だけを見ても、店全体として得をしたかは分かりません。クーポンで売上が前借りされ、配布後に反動で落ち込んでいるかもしれないからです。

そこで、クーポン配布前・配布中・配布後の3期間で、店全体の売上を並べます。

-- 配布前・配布中・配布後の3期間で全体売上を比較する
WITH daily AS (
  SELECT
    PARSE_DATE('%Y%m%d', event_date) AS dt,
    (SELECT ep.value.string_value
       FROM UNNEST(event_params) ep
      WHERE ep.key = 'transaction_id') AS transaction_id,
    ecommerce.purchase_revenue AS revenue
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260601' AND '20260831'
    AND event_name = 'purchase'
)

SELECT
  CASE
    WHEN dt BETWEEN '2026-06-01' AND '2026-06-30' THEN '1_配布前'
    WHEN dt BETWEEN '2026-07-01' AND '2026-07-31' THEN '2_配布中'
    ELSE '3_配布後'
  END AS period,
  COUNT(DISTINCT transaction_id) AS orders,
  ROUND(SUM(revenue)) AS total_revenue,
  ROUND(SUM(revenue) / COUNT(DISTINCT transaction_id)) AS avg_order_value
FROM daily
GROUP BY period
ORDER BY period;

ここで見たいのは、配布中の伸びが配布後の落ち込みで相殺されていないか、という点です。配布中に大きく伸びても、配布後にそれを打ち消すほど落ちていれば、需要を前借りしただけかもしれません。3期間を通したトータルで配布前を上回っているかどうかを確認します。

検証その3:対照群を使って差を見る

最も信頼できるのは、クーポンを配った集団(処置群)と、条件をそろえたうえで配らなかった集団(対照群)を比較する方法です。本来はクーポン配布の時点で、対象者をランダムに2グループへ分け、片方にだけ配ると理想的です。配信ツール側でグループ分けができる場合は、その割り当て情報をGA4のユーザープロパティや独自パラメータとして送っておきます。

ここで重要なのは、群の割り当てをユーザー単位で持つことです。treatment(配布あり)か control(配布なし)かは、購入したかどうかとは無関係に、対象者ひとりずつにあらかじめ決まっている属性です。これを購入イベントだけに付けてしまうと、各群の母数(分母)が購入者だけに偏り、購入率がまったく意味をなさなくなります。

理想は、配信ツール側が持つA/B割り当てを1ユーザー1行のテーブル(ここでは your_project.experiment.coupon_ab とします。user_idexp_group の2カラム)として用意し、購入実績とJOINする形です。GA4側で群をユーザープロパティとして送っている場合も、まずユーザー単位に1つの群を確定させてから集計します。

次のSQLは、割り当てテーブルを起点に各群の対象ユーザー全体を分母とし、そのうち購入したユーザーを分子として購入率を出します。

-- 処置群と対照群で、購入率・売上を比較する
-- 群の割り当てはユーザー単位(1ユーザー1行)で持つ前提
WITH assignment AS (
  -- A/B割り当てテーブル:user_id ごとに treatment / control が1つ決まっている
  SELECT user_id, exp_group
  FROM `your_project.experiment.coupon_ab`
  WHERE exp_group IN ('treatment', 'control')
),

purchases AS (
  -- 期間内の購入を user_id 単位で集計
  SELECT
    user_id,
    COUNT(DISTINCT
      (SELECT ep.value.string_value
         FROM UNNEST(event_params) ep
        WHERE ep.key = 'transaction_id')) AS orders,
    SUM(ecommerce.purchase_revenue) AS revenue
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260701' AND '20260731'
    AND event_name = 'purchase'
    AND user_id IS NOT NULL
  GROUP BY user_id
)

SELECT
  a.exp_group,
  -- 分母:各群の対象ユーザー全体
  COUNT(DISTINCT a.user_id) AS users,
  -- 分子:うち購入したユーザー
  COUNT(DISTINCT p.user_id) AS buyers,
  ROUND(
    COUNT(DISTINCT p.user_id) / COUNT(DISTINCT a.user_id),
    4
  ) AS conversion_rate,
  ROUND(SUM(IFNULL(p.revenue, 0))) AS total_revenue,
  ROUND(SUM(IFNULL(p.revenue, 0)) / COUNT(DISTINCT a.user_id), 1) AS revenue_per_user
FROM assignment a
LEFT JOIN purchases p
  ON a.user_id = p.user_id
GROUP BY a.exp_group
ORDER BY a.exp_group;

assignment が各群の対象ユーザー全体(買わなかった人も含む)を持ち、LEFT JOIN で購入実績を突き合わせています。これにより、分母は群ごとの全対象者、分子はそのうちの購入者となり、conversion_rate(購入率)が正しく計算できます。割り当てを購入イベント側から取っていたときのように、母数が購入者へ偏ることはありません。

両群はクーポンの有無以外の条件がそろっているはずなので、revenue_per_user(1人あたり売上)の差が、クーポンが生み出した上乗せに近いと考えられます。

たとえば処置群の1人あたり売上が対照群より高ければ、クーポンは売上を押し上げています。ほとんど差がなければ、クーポンは新たな購入をあまり生まず、配った割引の多くが自己食いだった可能性が高い、と読めます。

増分効果(インクリメンタル)の見方

ここまでの比較で得たいのは、最終的にはインクリメンタルな効果、つまり「クーポンがなければ発生しなかった売上」です。

対照群を使った場合、増分の売上はおおまかに次のように考えられます。

増分売上 = (処置群の1人あたり売上 − 対照群の1人あたり売上)× 処置群の人数

そして、この増分売上と、クーポンによって失った割引コストを比べます。割引コストには、上乗せを生んだ客への値引きだけでなく、もともと買ったはずの客に配ってしまった分も含まれます。後者はそのままカニバリゼーションのコストです。

判断の目安はシンプルです。

  • 増分によって得られた粗利 > 配った割引の総額:クーポンは利益面でもプラス
  • 増分によって得られた粗利 < 配った割引の総額:売上は増えても、利益は削られている

売上ベースで増えていても、粗利ベースではマイナスになることは珍しくありません。とくに割引率が高いクーポンや、常連客にも一律で配るクーポンほど、自己食いの割合が大きくなりがちです。インクリメンタルで見ることで、「売れた」と「得した」のズレを数字で確認できます。

注意:この検証で気をつけたいこと

最後に、検証を実務に使う際の注意点を挙げておきます。

対照群がない場合は近似でしかない

理想は処置群と対照群をランダムに分けることですが、配信ツールの制約で難しい場合もあります。その場合、検証その1や検証その2のような利用者比較・before/after比較で代用することになりますが、これらは因果関係の証明にはなりません。クーポンを使う人ともともと買う人の性質が違う、季節要因が混ざる、といった影響を完全には除けないからです。あくまで傾向をつかむ材料として扱ってください。

user_pseudo_id は完全な個人識別ではない

GA4の user_pseudo_id はブラウザやデバイスごとに振られるため、同じ人が複数端末を使えば別人としてカウントされます。ログイン情報に基づく user_id を送っている場合は、そちらを使うとより正確になります。

割引コストはGA4だけでは取り切れないことがある

クーポンの実際の割引額や原価は、GA4ではなく受注システムや在庫データ側にあることが多いです。粗利ベースでの判断をするなら、受注データとの突合が前提になります。突合の方法は冒頭で挙げた関連記事を参考にしてください。

期間設定で結果が変わる

需要の先食いがある以上、配布中だけを切り取ると効果を過大評価しがちです。配布後の反動まで含めた期間で見るようにしてください。

まとめ

クーポン施策は「配れば売れる」施策である一方、本来割引なしでも買ってくれた客にまで値引きを配ってしまう、カニバリゼーションのリスクを常に抱えています。だからこそ、見かけの売上ではなく、クーポンがなかった場合と比べた上乗せ(インクリメンタル)で評価することが大切です。

この記事で紹介した検証の流れを振り返ります。

  • 検証その1:利用者と非利用者の客単価を並べ、自己食いの感触をつかむ
  • 検証その2:配布前・中・後のbefore/afterで、需要の先食いを確認する
  • 検証その3:対照群との差から、クーポンの増分効果を近似する
  • 増分で得た粗利と、配った割引の総額を比べて、利益面で得をしたか判断する

GA4とBigQueryがあれば、これらの検証は自社のデータで実行できます。次にクーポンを打つときは、配布前にグループ分けの仕込みをしておくと、より確かな検証ができます。「売れた」で終わらせず、「本当に得したか」まで踏み込んで、クーポンの設計を一歩ずつ改善していきましょう。