dbt Semantic layer conversion metrics を試してみる

dbt Semantic layer conversion metrics を試してみる

2026.04.07

はじめに

dbt Semantic Layer(MetricFlow)の Conversion Metrics を試した際の内容を記事としました。

Conversion Metrics の概要

Conversion Metrics は、時間的な順序とウィンドウを考慮してエンティティを追跡するメトリクスタイプです。
より具体的には、「あるイベントを経験したエンティティが、一定期間内に別のイベントに至った割合」を測るメトリクスタイプとなります。

例えば「サイトを訪問したユーザーが、7日以内に購入に至った割合」などです。この例では、訪問というベースイベントと購入というコンバージョンイベントをユーザー ID 等で紐付け、一定のタイムウィンドウ内に両方が発生したかどうかを追跡します。

他にも以下のようなケースで使える指標と思います。

  • 広告クリック → 会員登録(登録コンバージョン率)
  • 会員登録 → 有料プランへのアップグレード(30日以内)
  • 注文 → 支払い完了(7日以内)

本機能については以下に記載があります。

https://docs.getdbt.com/docs/build/conversion?version=1.12

他のメトリクスタイプとの主な違いは以下の通りです。

タイプ 計算方法 特徴
ratio 分子 ÷ 分母(単純割り算) 同時点の集計値を割る
derived 既存メトリクスの組み合わせ 計算式を自由に定義
conversion ベース → コンバージョンをエンティティで追跡 時間的な順序とウィンドウを考慮できる

例として「返品率」は ratio で計算できますが、「注文から30日以内の返品率」の計算には Conversion Metrics が必要です(ratio では同時点の集計値を割るだけであるため、タイムウィンドウ内のエンティティ追跡ができない)。

内部処理

こちらも公式ドキュメントに記載がありますが、MetricFlow では以下のステップで Conversion Metrics を計算します。

  1. ベースとコンバージョンを JOIN:エンティティが一致し、かつコンバージョンがウィンドウ内に発生した組み合わせをすべて取得する。ここで1件のコンバージョンイベントに対して複数のベースイベントが条件を満たす場合、その数だけ行が生成される(ファンアウト)。
  2. 各コンバージョンを「最も近いベースイベント」に割り当て:ウィンドウ関数(FIRST_VALUE ... ORDER BY base_date DESC)で、ファンアウトが発生している行群に対して最も近いベースイベントの値を書き込み
  3. DISTINCT で重複を除去:ファンアウトが発生していた行群は、全て同じ内容(最近傍のベースイベント)に書き換わっているため、SELECT DISTINCT で1行に集約
  4. 集計して conversion_rate を計算:ベース側(opportunities)とコンバージョン側(conversions)を別々に集計し、FULL OUTER JOINで合算

前提条件

Conversion Metrics を使用するには以下が必要です。

  • 2つのイベントテーブル:Conversion Metrics で指定が必要なbase_metricconversion_metricオプションには、それぞれ COUNT 系のシンプルメトリクスを指定します。この2つのメトリクスは異なるセマンティックモデル(異なるテーブル)に定義されている必要があります
  • 共通のエンティティ:両テーブルを結ぶ共通のエンティティ(例:user_idorder_id)が必要です。また、両セマンティックモデルで指定するnameが一致している必要があります
    • 名前が異なると MetricFlow が join path を見つけられず、エラーとなります

YAML 設定

Conversion Metrics は、どのイベントを起点やコンバージョンとみなすか、何で紐付けるかなどを定義します。
代表的なオプションは以下の通りです。

オプション 内容 説明
entity 必須 2つのイベントを結ぶエンティティ名。両セマンティックモデルに同名のエンティティが必要
base_metric 必須 ベースイベントを表す COUNT 系のシンプルメトリクス。分母となる起点イベント数を表す
conversion_metric 必須 コンバージョンイベントを表す COUNT 系のシンプルメトリクス。分子となる変換イベント数を表す
window 任意 コンバージョンとみなすタイムウィンドウ(例:7 days)。省略すると無制限となる
calculation 任意 conversion_rate(割合、デフォルト)または conversions(件数)を指定

前提条件

検証環境

以下の環境を使用しています。

  • DWH:Snowflake
  • dbt platform
    • セマンティックレイヤーの記法は dbt Fusion エンジンおよび dbt platform Latest リリーストラックで利用可能な新しい YAML 記法を使用しています

https://docs.getdbt.com/docs/build/latest-metrics-spec?version=1.12

事前準備

検証用 Seed データを使います。Seeds に直接セマンティックモデルを定義できないため、Seeds を参照するモデルも用意します。

サンプルデータで動作を確認

ここでは「サイト訪問(sample_visits)から購入(sample_purchases)への7日以内のコンバージョン」を追跡する設定とします。
entityuser(ユーザー軸)と visit(訪問軸)の2パターンを定義し、結果の違いを比較します。

サンプルデータの用意

以下の CSV をseeds/に配置します。

seeds/sample_visits.csv
visit_id,user_id,visit_date
1,u001,2024-01-01
2,u001,2024-01-03
3,u002,2024-01-01
4,u003,2024-01-01
5,u004,2024-01-07
seeds/sample_purchases.csv
purchase_id,user_id,visit_id,purchase_date
1,u001,2,2024-01-05
2,u002,3,2024-01-04
3,u004,5,2024-01-15

sample_purchasesにはvisit_id(訪問との直接紐付け)とuser_id(ユーザー単位の紐付け)の両方を持たせています。これにより、entityオプションの挙動の違いを確認します。

各モデルとエンティティの対応関係は以下の通りです。

モデル エンティティ種別 名前 カラム
smp_visits primary visit visit_id
smp_visits foreign user user_id
smp_purchases primary purchase purchase_id
smp_purchases foreign visit visit_id
smp_purchases foreign user user_id

Seed からテーブルを作成しておきます。

dbt seed --select "sample_visits sample_purchases"

また、Seeds に直接セマンティックモデルを定義できないため、Seeds を参照するモデルを作成します。

models/smp_visits.sql
select * from {{ ref('sample_visits') }}
models/smp_purchases.sql
select * from {{ ref('sample_purchases') }}
dbt build --select "smp_visits smp_purchases"

セマンティックモデルと Conversion Metrics の定義

異なる entity(entity: userentity: visit)の2パターンを同時に定義し、結果を比較できるようにします。

models/sample_semantic.yml
models:
  - name: smp_visits
    semantic_model:
      enabled: true
    agg_time_dimension: visit_date
    columns:
      - name: visit_id
        entity:
          type: primary
          name: visit
      - name: user_id
        entity:
          type: foreign
          name: user  # smp_purchases の foreign entity と対応
      - name: visit_date
        granularity: day
        dimension:
          type: time

    metrics:
      # base_metric として使用:conversion の「分母(起点イベント数)」になる
      - name: visit_count
        type: simple
        label: 訪問数
        agg: count
        expr: 1

      # --- entity: user を軸にした Conversion(ファンアウトあり)---
      # JOIN キー = user_id。同一ユーザーの複数訪問が同一購入に紐付く → ファンアウト発生
      - name: visit_to_purchase_conversion_rate_by_user
        type: conversion
        label: 訪問→購入コンバージョン率・ユーザー軸(7日)
        entity: user           # JOIN キー:両テーブルの user_id(foreign entity)を軸に結合
        calculation: conversion_rate
        base_metric: visit_count        # smp_visits で定義したメトリクスを参照
        conversion_metric: purchase_count  # smp_purchases で定義したメトリクスを参照
        window: 7 days

      - name: visit_to_purchase_conversions_by_user
        type: conversion
        label: 訪問→購入コンバージョン件数・ユーザー軸(7日)
        entity: user
        calculation: conversions
        base_metric: visit_count
        conversion_metric: purchase_count
        window: 7 days

      # --- entity: visit を軸にした Conversion(1対1対応・ファンアウトなし)---
      # JOIN キー = visit_id。purchases.visit_id が直接指定した訪問のみが対象
      - name: visit_to_purchase_conversion_rate_by_visit
        type: conversion
        label: 訪問→購入コンバージョン率・訪問軸(7日)
        entity: visit          # JOIN キー:visit_id(primary/foreign entity)を軸に結合
        calculation: conversion_rate
        base_metric: visit_count
        conversion_metric: purchase_count
        window: 7 days

      - name: visit_to_purchase_conversions_by_visit
        type: conversion
        label: 訪問→購入コンバージョン件数・訪問軸(7日)
        entity: visit
        calculation: conversions
        base_metric: visit_count
        conversion_metric: purchase_count
        window: 7 days

  - name: smp_purchases
    semantic_model:
      enabled: true
    agg_time_dimension: purchase_date
    columns:
      - name: purchase_id
        entity:
          type: primary
          name: purchase
      - name: user_id
        entity:
          type: foreign
          name: user  # smp_visits の foreign entity と対応
      - name: visit_id
        entity:
          type: foreign
          name: visit  # smp_visits の primary entity と対応 → entity: visit での JOIN に使用
      - name: purchase_date
        granularity: day
        dimension:
          type: time
    metrics:
      # conversion_metric として使用:conversion の「分子(変換イベント数)」になる
      - name: purchase_count
        type: simple
        label: 購入数
        agg: count
        expr: 1

conversion メトリクス内のentity オプション

conversion メトリクスのentityは「何を追跡対象とするか」を決めるオプションとなります。
entity: userentity: visitによって、「訪問したユーザーが購入したか(ユーザー単位の追跡)」なのか「その訪問が直接購入につながったか(訪問単位の追跡)」なのかを指定できます。

calculation オプション

YAML 内のcalculationオプションにはconversion_rateconversionsのいずれかを指定できます。それぞれ以下の通りです。

calculation 返す値 計算式
conversion_rate コンバージョン率(0〜1以上の小数) conversions ÷ opportunities
conversions コンバージョン件数(整数) ウィンドウ内に変換されたベースイベントの件数

どちらも同じ base/conversion/window の設定を使い、集計の最終ステップでの出力形式だけが異なります。用途に応じて使い分けるか、上記のように両方定義しておくことで割合と件数を同時に取得できます。

クエリ実行と結果確認

entity: user(ユーザー軸)と entity: visit(訪問軸)の4メトリクスを同時にクエリして比較します。

dbt sl query \
  --metrics visit_to_purchase_conversion_rate_by_user,visit_to_purchase_conversions_by_user,visit_to_purchase_conversion_rate_by_visit,visit_to_purchase_conversions_by_visit \
  --group-by metric_time__day

実行結果は以下の通りです。

$ dbt sl query \
  --metrics visit_to_purchase_conversion_rate_by_user,visit_to_purchase_conversions_by_user,visit_to_purchase_conversion_rate_by_visit,visit_to_purchase_conversions_by_visit \
  --group-by metric_time__day

+-------------------------+-------------------------------------------+--------------------------------------------+---------------------------------------+----------------------------------------+
| METRIC_TIME__DAY        | VISIT_TO_PURCHASE_CONVERSION_RATE_BY_USER | VISIT_TO_PURCHASE_CONVERSION_RATE_BY_VISIT | VISIT_TO_PURCHASE_CONVERSIONS_BY_USER | VISIT_TO_PURCHASE_CONVERSIONS_BY_VISIT |
+-------------------------+-------------------------------------------+--------------------------------------------+---------------------------------------+----------------------------------------+
| 2024-01-07T00:00:00.000 | <nil>                                     | <nil>                                      | <nil>                                 | <nil>                                  |
| 2024-01-03T00:00:00.000 | 1                                         | 1                                          | 1                                     | 1                                      |
| 2024-01-01T00:00:00.000 | 0.6666666667                              | 0.3333333333                               | 2                                     | 1                                      |
+-------------------------+-------------------------------------------+--------------------------------------------+---------------------------------------+----------------------------------------+

2つの entity による結果の違いをまとめると以下の通りです。

日付 by_user rate by_user conversions by_visit rate by_visit conversions 差異の理由
Jan 1 0.667 2 0.333 1 Jan 1 の訪問は3件(u001・u002・u003)。user 軸では u001(visit 1)と u002(visit 3)の2件がコンバージョン → 2/3。visit 軸では purchases.visit_id が存在する visit 3(u002)の1件のみ → 1/3
Jan 3 1.0 1 1.0 1 visit 2(u001)→ purchases.visit_id=2(Jan 5, 2日後)。両軸で一致
Jan 7 nil nil nil nil visit 5(u004)→ purchase(Jan 15)は8日後でウィンドウ外

また、参考までに、もう少し詳細に結果がどのように計算されるか以下にまとめます。

ベース側(分母)

どちらの entity でも、分母となる opportunities は「その日の訪問件数」となります。

日付 訪問 opportunities
Jan 1 visit 1(u001)・visit 3(u002)・visit 4(u003) 3
Jan 3 visit 2(u001) 1
Jan 7 visit 5(u004) 1

コンバージョン側(分子)

こちらは entity によって変わります。
entity: visitvisit_idで直接 JOIN するため、purchases テーブルにvisit_idが存在する訪問だけがコンバージョン済みとみなされます。

visit 1(u001, Jan 1)→ purchases に visit_id=1 の行なし → 未コンバージョン
visit 2(u001, Jan 3)→ purchases に visit_id=2(Jan 5, 2日後)→ コンバージョン
visit 3(u002, Jan 1)→ purchases に visit_id=3(Jan 4, 3日後)→ コンバージョン
visit 4(u003, Jan 1)→ purchases に visit_id=4 の行なし → 未コンバージョン
visit 5(u004, Jan 7)→ purchases に visit_id=5(Jan 15, 8日後)→ ウィンドウ外

entity: useruser_idで JOIN するため、「その訪問時点から7日以内にそのユーザーが購入したか」を各訪問に対して独立して評価します。

visit 1(u001, Jan 1)→ u001 が Jan 5 に購入(4日後) → コンバージョン ← entity:visit と差異が出る
visit 2(u001, Jan 3)→ u001 が Jan 5 に購入(2日後) → コンバージョン
visit 3(u002, Jan 1)→ u002 が Jan 4 に購入(3日後) → コンバージョン
visit 4(u003, Jan 1)→ 購入なし → 未コンバージョン
visit 5(u004, Jan 7)→ u004 が Jan 15 に購入(8日後) → ウィンドウ外

opportunities と conversions を日付別に集計して割る

最終的には、opportunities と conversions を日付別に集計して割っています。

entity Jan 1 conversions Jan 1 opportunities rate
visit 1(visit 3 のみ) 3 0.333
user 2(visit 1 + visit 3) 3 0.667

visit 1(u001, Jan 1)が entity: user ではコンバージョン済み、entity: visitでは未コンバージョンとなることが、Jan 1 の値が異なる原因です。

上記より、entity: visitでは「その訪問に直接紐づく購入が7日以内に発生したか」を厳密に測りたい場合に適しています。一方で、entity: uservisit_idがなくユーザー単位でしか紐付けられない場合や、訪問単位の厳密な紐付けが不要な場合に適しています。

さいごに

dbt Semantic Layer の Conversion Metrics の動作を確認してみました。
こちらの内容がどなたかの参考になれば幸いです。

この記事をシェアする

関連記事