dbtのmarts層は、staging層・intermediate層で整えたデータを「注文」「顧客」といった業務エンティティ単位のテーブルにまとめる最終層です。dbt Labsの公式ガイドは、martsをordersやcustomersのようにエンティティ名で命名し、テーブルかincrementalモデルで実体化することを推奨しています。
ただし、MetricFlow(dbt Semantic Layer)で指標を定義するかどうかで、martsの作り方は逆になります。使わないなら横に広い非正規化テーブル、使うなら正規化寄りです。この記事では3層の責務と命名規則を整理したうえで、dbt 1.12で変わった新しいYAML仕様でmartsに指標を載せる手順を、dbt-core 1.12.5とDuckDBで実際に動かした結果とともに説明します。dbtそのものの仕組みはdbtとは?ELTのTを担う仕組みとdbt Core・Cloudの選び方を先に読むと流れがつかめます。
まとめ:marts層の役割と指標定義の要点
- martsはエンティティ粒度:1行=1注文、1行=1顧客のように粒度を固定します。日次集計(
orders_per_day)のように時間で丸めた時点で、それはmartsではなく指標の仕事です。 - 3層の命名:staging=
stg_[source]__[entity]s、intermediate=int_[entity]s_[verb]s、marts=ordersのような平易なエンティティ名が公式ガイドの形です。fct_・dim_接頭辞はKimball流の慣習で、混在させなければ使ってかまいません。 - Semantic Layerを使うなら正規化寄り:MetricFlowはクエリ時に結合して非正規化するエンジンなので、martsで集計・結合を固めるほど切り口が減ります。
- dbt 1.12で書き方が変わった:
measuresとtype_paramsは非推奨(deprecated)となって新仕様では使わず、semantic_modelはモデル定義の配下に入りました。simple指標はモデル内に書き、別のsemantic modelをまたぐratio・cumulative・derived・conversionはトップレベルのmetricsに書きます。 - 実行環境:dbt Coreなら
pip install dbt-metricflowでmfコマンドを使えます(Apache-2.0)。BIツールからAPIで指標を引くSemantic Layerはdbt platformのStarter($100/ユーザー/月)以上です。
staging・intermediate・martsの責務と命名規則
dbt Labsの「How we structure our dbt projects」は、下流ほどソースの都合から離れ業務の言葉に近づく3層を勧めています。
| 層 | 役割 | 命名 | 推奨マテリアライズ | やらないこと |
|---|---|---|---|---|
| staging | ソース1表を整える | stg_[source]__[entity]s | view | 結合・集計 |
| intermediate | 結合・粒度変更の途中工程 | int_[entity]s_[verb]s | ephemeral または別スキーマのview | エンドユーザーへの公開 |
| marts | 業務エンティティの完成形 | orders、customers | table、遅ければincremental | 部門別の重複定義 |
staging層:改名・型変換・単純計算に限る責務
stagingで許されるのは改名、型変換、単位換算のような単純計算(セント→ドル)、条件分岐による区分けまでです。結合と集計は公式ガイドが明示的に避けるよう書いています。結合すると同じ計算が下流で重複し、モデル同士の関係も追いにくくなります。集計すると粒度が変わり、後工程で必要になる元の行に戻れなくなるからです。ディレクトリはソースシステム単位(社内DB、Stripe、Snowplowなど)で切り、部門単位では切りません。
命名のstg_google_analytics__campaignsのような二重アンダースコアは、ソース名とエンティティ名の境界を見分けるためのものです。stg_campaignsではソースが増えた時点で出どころが分からなくなります。
intermediate層:動詞で名付ける途中工程
intermediateは、martsへ組み上げる前の結合・粒度変更・複雑な処理の切り出しを担います。名前は公式例のint_orders_summed_to_customerのように「何をしたか」を動詞(pivoted、joined、aggregated_to_userなど)で表します。公式ガイドのmarts章は、厳密な規則ではないとしつつ、1つのmartで4〜5個を超える概念を結合するならintermediateを挟むと読みやすくなるとしています。
intermediateは利用者に公開しない前提なので、既定はephemeral(CTEとして展開)で十分です。中身を確認したくなったら、権限を絞った別スキーマのviewに切り替えます。
marts層:エンティティ粒度と部門別定義の回避
martsは「1行が何を表すか」をエンティティで決めます。ordersには顧客属性を結合して持たせてよいものの、行はあくまで1注文です。顧客と注文を日付で束ねてuser_orders_per_dayを作った時点で、公式ガイドはそれを「martsを越えてmetricsに入っている」と表現しています。
避けるべきなのは、同じ概念を部門ごとに作り分けるfinance_ordersとmarketing_ordersです。財務報告用に売上の定義が本当に違うなら、tax_revenueとrevenueのように別概念として名付けます。martsが10本前後を超えてサブフォルダが要るときは、marketing・financeのような部門単位のフォルダ分けを公式ガイドも一般的な構成として挙げています。避けるのはフォルダではなく定義の重複です。マテリアライズは「viewで始め、参照が遅くなったらtable、ビルドが遅くなったらincremental」の順に上げます。
fct_・dim_接頭辞を使うかどうかの判断
公式ガイドのサンプルプロジェクトはcustomers.sql・orders.sqlのように接頭辞なしです。一方、Kimballのディメンショナルモデリングに慣れたチームでは、事実をfct_orders、属性をdim_customersと分ける命名が広く使われています。
どちらでも動作は変わりません。問題になるのは混在です。ordersとfct_paymentsが同じフォルダに並ぶと、接頭辞の有無に意味があるのかを読み手が推測しなければなりません。Semantic Layerを入れる予定があるなら、後述のとおり事実と属性を分けたスタースキーマ寄りの形が合うため、fct_・dim_で統一するほうが意図を伝えやすくなります。スタースキーマの設計手順そのものはBEAM✲とは?7Wで進めるアジャイルなディメンショナルモデリングの手順とJEDUFで扱っています。
Semantic Layerの有無でmartsの設計方針が逆になる理由
公式のmarts章の冒頭には、Semantic Layerを使うかどうかで推奨が分かれるという注記があります。使わない場合は「大きく非正規化(denormalize heavily)」、使う場合は「できるだけ正規化」です。
理由は、MetricFlowが非正規化をクエリ時に肩代わりするエンジンだからです。entityで結合関係を宣言しておけば、MetricFlowは要求された切り口に応じてJOINを組み立てます。martsで先に結合・集計して固めると、固めた切り口以外では集計できなくなります。正規化と非正規化の一般的な判断軸はデータベース正規化とは?第1〜第3正規形の手順と非正規化の判断を解説を参照してください。
集計済みmartに保存した比率による全体値のずれ
martsで月次の完了率を計算して保存し、BI側で「期間の完了率」を出そうとすると、月ごとの率を平均してしまう事故が起きます。検証用の6注文(9月5件・10月1件)での比較です。
| 計算方法 | 結果 |
|---|---|
| 9月の完了率(4,300÷4,800) | 0.8958 |
| 10月の完了率(900÷900) | 1.0000 |
| 月次の率を平均(martsに率を保存した場合) | 0.9479 |
| 合計どうしの比(MetricFlowのratio指標) | 0.9123 |
MetricFlowのratio指標は、分子と分母をそれぞれ集計してから割るので、月で切っても年で切っても全体でも正しい値になります。比率・平均・ユニーク数のように足し合わせられない指標は、martsに結果を保存せず、指標として定義するのが原則です。
既存の集計martをSemantic Layerへ移す8手順
公式ガイド「Refactor an existing rollup」は、出力単位で段階的に移す手順を示しています。
- ダッシュボードの売上グラフなど、重要な出力と、それを支えるmartを1つ選ぶ
- そのmartが材料にしているエンティティ(顧客・配送・商品など)を洗い出す
- 材料側のmartそれぞれにsemantic modelを作る
- 集計に必要な指標を定義する
- Semantic Layer上で同じ出力を複製する
- 既存出力と数値が一致するか監査する
- 同じ集計martを参照している他の出力も移す
- 不要になった集計martの廃止計画を立てる
同ガイドは、計測値を持つのに時刻列を持たないモデル(例:商品の原価表)は、時刻を持つモデルへ結合してから載せるよう勧めています。同ガイドが正規化を崩す例として挙げているのは、このように指標の時間軸を確保するケースです。
MetricFlowでmartsに指標を定義する手順(dbt 1.12の新YAML仕様)
ここからは、注文(fct_orders)と顧客(dim_customers)の2つのmartに指標を載せる最小構成で手順を示します。コードは2026年10月に下の環境で実行し、すべて成功したものです(dbt build時に、revenue_mtdの累計範囲をOSI documentに表現できないという警告が出ますが、MetricFlowの検証とクエリには影響しません)。
動作環境とMetricFlowの導入
| パッケージ | 版 | 公開日(PyPI) |
|---|---|---|
| dbt-core | 1.12.5 | 2026-09-15 |
| dbt-metricflow | 0.15.0 | 2026-09-10 |
| metricflow | 0.213.0 | 2026-09-10 |
| dbt-duckdb | 1.11.0 | 2026-08-07 |
dbt Core環境では、アダプター名を角括弧で指定して入れると、dbt本体・アダプター・MetricFlowの組み合わせがそろいます。
python -m venv .venv
. .venv/bin/activate
pip install "dbt-metricflow[dbt-duckdb]"
3層のモデルとフォルダ別のマテリアライズは、dbt_project.ymlで一括指定します。
name: shop
version: "1.0.0"
profile: shop
models:
shop:
staging:
+materialized: view
intermediate:
+materialized: ephemeral
marts:
+materialized: table
累積指標に必須のtime spineモデル
MetricFlowは日付の穴埋めや累積指標の計算に、1日1行の日付表(time spine)を使います。cumulative指標を定義する前に必須です。DuckDBならrange関数で作れます。
-- models/marts/metricflow_time_spine.sql
select cast(range as date) as date_day
from range(date '2026-01-01', date '2027-01-01', interval 1 day)
martsモデルへのentity・dimension・simple指標の記述
注文のmartは、stagingから1行=1注文のまま必要な列だけを選びます。顧客の地域はここに持たせません。
-- models/marts/fct_orders.sql
select
order_id,
customer_id,
ordered_at,
status = 'completed' as is_completed,
amount
from {{ ref('stg_shop__orders') }}
新仕様では、semantic modelをmartsモデルの定義に同居させます。結合キーの列にentity、切り口の列にdimensionを付け、単一列の集計はモデル配下のmetricsにtype: simpleで書きます。
# models/marts/_marts.yml
models:
- name: metricflow_time_spine
time_spine:
standard_granularity_column: date_day
columns:
- name: date_day
granularity: day
- name: fct_orders
semantic_model:
enabled: true
agg_time_dimension: ordered_at
columns:
- name: order_id
entity:
type: primary
name: order
data_tests: [unique, not_null]
- name: customer_id
entity:
type: foreign
name: customer
- name: ordered_at
granularity: day
dimension:
type: time
metrics:
- name: revenue
label: 売上(全注文)
type: simple
agg: sum
expr: amount
- name: completed_revenue
label: 売上(完了のみ)
type: simple
agg: sum
expr: case when is_completed then amount else 0 end
- name: order_count
label: 注文数
type: simple
agg: count
expr: order_id
- name: dim_customers
semantic_model:
enabled: true
columns:
- name: customer_id
entity:
type: primary
name: customer
- name: region
dimension:
type: categorical
aggに指定できるのはsum・max・min・average・median・count・count_distinct・percentile・sum_booleanの9種類です。agg_time_dimensionはモデル単位の既定の時間軸で、指標ごとに上書きできます。
旧仕様(measures・type_params)からの書き換え対応
新仕様が使えるのはdbt v1.12、dbt v2、dbt platformのLatestリリーストラックです。それより前のdbt Coreを使っている場合や、ネット上の解説記事を読む場合は、どちらの仕様で書かれているかを最初に見分けてください。
| 項目 | 旧仕様 | 新仕様(dbt 1.12〜) |
|---|---|---|
| semantic modelの位置 | トップレベルのsemantic_models | models配下のsemantic_model |
| 集計の定義 | measures | type: simple の指標(agg・expr) |
| entity・dimension | semantic model内に列挙 | 各列に付ける |
| 時間の粒度 | type_params.time_granularity | 列のgranularity |
| 指標のパラメータ | type_paramsで包む | 指標の直下に書く |
| derivedの入力 | type_params.metrics | input_metrics |
| cumulativeの入力 | measure | input_metric |
dbt 1.12.5では旧仕様のファイルもそのまま解析できます。ただし旧仕様のcumulative指標でgrain_to_dateをtype_params直下に置くと、「cumulative_type_paramsの下に入れること」という警告に続いてParsing Errorで止まりました。古い記事のサンプルを貼る場合は、この位置を確認してください。旧仕様のYAMLは、公式ドキュメントが案内するdbt-autofix deprecations --semantic-layerで新仕様へ書き換えた差分を確認できます。
新仕様には制約が1つあります。dbt Meshで別プロジェクトのモデルをrefする構成は、新仕様のsemantic modelではまだ使えず、旧仕様でだけ動きます。複数プロジェクト構成なら移行を急がないでください。dbt Coreで複数プロジェクトを扱う方法はdbt-loomとは?dbt Coreでプロジェクト間refを使う設定と制約にまとめています。
mf validate-configsとmf queryによる検証結果
dbt build(またはdbt parse)でsemantic manifestを作ったあと、mf validate-configsで定義の整合性と、ウェアハウス上の実データに対する検証を行います。手元の構成ではsemantic model・dimension・entity・指標の各段階がERRORS 0で通りました。
dbt build
mf validate-configs
mf query --metrics revenue,completed_revenue,completion_rate,avg_order_value --group-by metric_time__month --order metric_time__month
metric_time__month revenue completed_revenue completion_rate avg_order_value
-------------------- --------- ------------------- ----------------- -----------------
2026-09-01T00:00:00 4800 4300 0.895833 960
2026-10-01T00:00:00 900 900 1 900
--explainを付けると、MetricFlowが組み立てたSQLだけを表示します。CIで指標を壊していないかを見るときは、mf validate-configsをジョブに入れておくのが手軽です。dbt platformでは同じ検証をdbt sl validateで実行できます。
metricの5種類と選び分け
simple以外の4種類は、別の指標を材料にする「指標の指標」です。材料が1つのsemantic modelに収まるならモデル配下のmetricsにも書けますが、別のsemantic modelの指標や切り口を参照するときはトップレベルのmetricsが必須です(simpleはトップレベルに書けません)。
| 種類 | 用途 | 必須パラメータ |
|---|---|---|
| simple | 1列の集計 | agg |
| ratio | 合計どうしの比率 | numerator、denominator |
| cumulative | 累計・移動累計・期首来累計 | input_metric |
| derived | 指標どうしの四則演算・前期比 | expr、input_metrics |
| conversion | 行動AからBへの転換率 | entity、calculation、base_metric、conversion_metric |
metrics:
- name: completion_rate
label: 完了率
type: ratio
numerator: completed_revenue
denominator: revenue
- name: revenue_mtd
label: 月初来売上
type: cumulative
grain_to_date: month
input_metric: revenue
- name: avg_order_value
label: 平均注文額
type: derived
expr: revenue / orders
input_metrics:
- name: revenue
- name: order_count
alias: orders
ratioとderivedの使い分け
「完了売上÷全売上」のような比率は、どちらでも書けます。どちらも入力指標ごとにfilterを付けられ、入力指標をそれぞれ集計してから計算します。違いは式の自由度で、ratioは分子と分母を指定するだけの専用形式です。derivedはexprに任意の式を書けるので、「売上−返品」やoffset_window: 7 daysを使った前週比のように、比率以外の計算に使います。上のavg_order_valueは売上÷注文数で、9月は4,800÷5=960と正しく出ました。
cumulativeの累計範囲を決めるwindow・grain_to_date・period_agg
cumulativeは3通りの累計を作れます。何も指定しなければ全期間の累計、window: 7 daysなら直近7日の移動累計、grain_to_date: monthなら月初でリセットされる月初来累計です。windowとgrain_to_dateは同時に指定できません。
mf query --metrics revenue_mtd --group-by metric_time__day --where "{{ TimeDimension('metric_time', 'day') }} between '2026-09-28' and '2026-10-02'" --order metric_time__day
metric_time__day revenue_mtd
------------------- -------------
2026-09-28T00:00:00 4800
2026-09-29T00:00:00 4800
2026-09-30T00:00:00 4800
2026-10-01T00:00:00 900
2026-10-02T00:00:00 900
10月1日で4,800から900へ戻っており、月初でリセットされているのが分かります。日次で定義した累計を月単位で取り出すときは、月内のどの日の値を代表にするかをperiod_agg(first・last・average、既定はfirst)で決めます。月末残高のように使うならlastを明示しないと、月初の値が返ります。
entityの4種類と結合設計の注意点
entityは、MetricFlowがsemantic model同士の結合可否を判断する材料で、4種類あります。
- primary:1行に1つで、そのモデルに全レコードがそろう(例:
fct_ordersのorder_id) - unique:1行に1つだが、全レコードの一部しか持たずNULLもありうる
- foreign:同じ値が0回・1回・複数回現れてよく、NULLもありうる(例:
fct_ordersのcustomer_id) - natural:業務上の値で行を一意に決めるキー(公式例:営業担当の部署表
sales_person_departmentのsales_person_id)
属性側のprimary entity欠落で起きる解決エラー
手元の検証では、最初にdim_customersのsemantic modelを作らずにmf query --metrics revenue --group-by customer__regionを実行し、「利用可能なgroup-by項目に一致しない」というエラーで止まりました。fct_orders側でcustomerをforeignとして宣言しても、結合先としてcustomerをprimaryに持つモデルが無ければ、MetricFlowは顧客属性へたどり着けません。
dim_customersにcustomerのprimary entityとregionのdimensionを足すと、MetricFlowは次のLEFT OUTER JOINを自動で組み立てました(--explainの出力)。
SELECT
dim_customers_src_10000.region AS customer__region
, SUM(fct_orders_src_10000.amount) AS revenue
FROM "shop"."main"."fct_orders" fct_orders_src_10000
LEFT OUTER JOIN
"shop"."main"."dim_customers" dim_customers_src_10000
ON
fct_orders_src_10000.customer_id = dim_customers_src_10000.customer_id
GROUP BY
dim_customers_src_10000.region
martsを正規化しておく効果はここに出ます。fct_ordersにregionを持たせなくても、顧客の地域で売上を切れるからです。エラーメッセージの「fan-out join support is pending」は、原因候補を並べた定型リストの1行です。MetricFlowは、1行が相手側の複数行に結合されて行数が増える結合(fan-out。foreign→foreignやprimary→foreign)を許可しません。属性を引きたい側には、結合キーをprimaryかuniqueとして持つモデルを用意します。
dbt Core(mf)とdbt platform(dbt sl)の違いと料金
MetricFlow本体はPyPIで配布されているPythonライブラリで、0.209.0(2025年10月)以降はApache-2.0です。dbt Coreと組み合わせればローカルで指標の定義・検証・SQL生成まで無料で使えます。料金が発生するのは、BIツールやアプリケーションからAPIで指標を引く「dbt Semantic Layer」のほうです。
| 項目 | セルフホスト(dbt Core) | dbt platform |
|---|---|---|
| コマンド | mf | dbt sl |
| 実行場所 | 手元のマシン | dbt platform上でリモート実行 |
| MetricFlowの版管理 | 自分でpip管理 | platform側が管理 |
| 外部ツールからのAPI接続 | なし | Starter以上 |
| 指標クエリの上限 | なし | Starter 5,000件/月、Enterprise 20,000件/月 |
Starterは1ユーザーあたり月額$100で、開発者席5つ・1プロジェクトまでです。無料のDeveloperプランではSemantic LayerのAPIは使えません。dbt Coreで定義した指標をBIに出したいだけなら、dbtのYAMLから指標を読むOSSのBIを組み合わせる方法もあります(Lightdash(ライトダッシュ)とは?dbtで指標を定義するOSS BIの仕組み・料金・導入手順)。指標の定義元が2つに割れないよう、どちらか一方に寄せてください。
指標が参照するmartsの列が上流の変更で消える事故は、dbtのモデル契約で防げます(データ契約(Data Contract)とは?ODCSとdbtでスキーマ破壊を止める実装を解説)。
よくある質問
dbtのmartsとは何ですか?
staging層・intermediate層で整えたデータを、注文・顧客などの業務エンティティ単位にまとめた最終層のモデルです。BIツールや分析者が直接参照する前提で、テーブルかincrementalモデルとして実体化します。
staging・intermediate・martsはどう使い分けますか?
stagingはソース1表ごとの改名・型変換、intermediateは結合や粒度変更の途中工程、martsは完成したエンティティです。stagingで結合・集計をせず、intermediateを利用者に公開しなければ境界は崩れません。
martsにfct_とdim_の接頭辞は必要ですか?
必須ではありません。公式ガイドのサンプルはorders・customersの平易な名前です。Kimball流のfct_・dim_を使うならプロジェクト全体で統一し、接頭辞ありとなしを混在させないでください。
dbt 1.12でmeasuresは使えなくなりましたか?
新しいYAML仕様では非推奨(deprecated)となり、type: simpleの指標にaggとexprを書く形に置き換わりました。dbt 1.12.5でも旧仕様のファイルは解析できますが、dbt Meshのクロスプロジェクト参照を使っていなければ、新仕様への移行を計画してください。
MetricFlowは無料で使えますか?
MetricFlowは2025年10月14日公開の0.209.0からライセンスがBUSL-1.1からApache-2.0に変わり、dbt Coreと組み合わせてmfコマンドで使う範囲は無料です。外部ツールからAPIで指標を参照するdbt Semantic Layerは、dbt platformのStarter以上の有料プランが必要です。