dbt Metrics Layer: Defining Business Metrics Once, Reusably
Ask five analysts to write a query for โmonthly active usersโ and youโll likely get five subtly different definitions โ different date boundaries, different inclusion criteria for what counts as โactive,โ different handling of edge cases. Every one of them might be defensible individually, and the resulting inconsistency between dashboards is one of the most common sources of โwhy do these two reports show different numbers for the same metricโ disputes. dbtโs metrics layer exists to define a metricโs logic exactly once, as code, so every tool querying it gets the same answer.
The Problem: Metric Logic Scattered Everywhere
Without a metrics layer, โmonthly revenueโ logic typically lives in multiple places simultaneously: hardcoded in a Looker LookML file, redefined slightly differently in a Tableau calculated field, and written a third way directly in an analystโs ad hoc SQL query. Each definition can drift independently, and thereโs no single source of truth to reconcile them against when numbers disagree.
Defining a Metric
metrics: - name: monthly_revenue label: "Monthly Revenue" model: ref('sales_summary') description: "Total completed order revenue, recognized monthly." calculation_method: sum expression: revenue timestamp: month time_grains: [month, quarter, year] filters: - field: order_status operator: '=' value: "'completed'"This single YAML declaration says, precisely: revenue means the sum of the revenue column from sales_summary, only counting completed orders, aggregatable by month, quarter, or year. Any tool querying this metric definition gets identical logic โ no more reconciling five slightly different SQL queries that all claim to compute โrevenue.โ
Querying a Defined Metric
Rather than writing raw SQL against the underlying table, metrics are queried through dbtโs metric interface, which compiles the correct SQL from the declaration automatically.
dbt run-operation calculate_metric --args '{"metric": "monthly_revenue", "grain": "month"}'The exact invocation pattern depends on your dbt version and whether youโre using the newer Semantic Layer (covered in Semantic Layer), but the core idea holds across implementations: the metricโs calculation logic lives in one YAML definition, queried consistently rather than re-implemented per consumer.
Metrics Built From Other Metrics
A more advanced pattern lets you derive new metrics from existing ones, rather than redefining the underlying logic each time.
metrics: - name: revenue_per_customer label: "Revenue Per Customer" calculation_method: derived expression: "{{ metric('monthly_revenue') }} / {{ metric('active_customer_count') }}"This guarantees revenue_per_customer always uses the exact same monthly_revenue and active_customer_count definitions declared elsewhere โ if either underlying metricโs logic changes, this derived metric automatically reflects the update, rather than needing its own separate maintenance.
Why This Belongs in dbt, Not in the BI Tool
The instinctive place to define a metric is inside the BI tool itself โ a Looker measure, a Tableau calculated field. The problem is that metric logic then only exists for users of that specific tool. A data scientist querying the warehouse directly from a notebook, or a second BI tool the company adopts later, has no access to that definition and has to reimplement it independently โ exactly the drift problem this feature exists to prevent. Defining metrics in dbt, adjacent to the models and tests that already define what the data means, keeps the definition tool-agnostic.
Metrics and Testing
Because a metric definition is just YAML referencing an underlying model and column, it inherits the reliability of whatever testing exists on that model โ a not_null and range test on the revenue column, covered in Generic Tests and Custom Tests, directly protects the accuracy of any metric built on top of it.
Metrics Layer vs. Semantic Layer: A Quick Distinction
The Metrics Layer, covered here, is specifically about defining individual metric calculations. dbtโs broader Semantic Layer, covered in Semantic Layer, builds on top of this with a more complete semantic model โ dimensions, entities, and multi-tool query serving via MetricFlow โ solving the same underlying consistency problem at a larger architectural scale.
| Metrics Layer | Semantic Layer | |
|---|---|---|
| Scope | Individual metric definitions | Full semantic model โ metrics, dimensions, entities |
| Solves | โDefine this calculation once" | "Serve consistent metrics to any tool that asksโ |
Documenting Metrics Alongside Models
Metrics benefit from the same descriptive documentation practice as models and columns โ a description field on each metric definition, surfaced in the documentation site covered in dbt Docs Generate, gives anyone querying the metric a clear, authoritative explanation of exactly what it measures.
metrics: - name: monthly_revenue description: > Total completed order revenue, recognized in the month the order was marked complete (not the month it was placed, if different). Excludes cancelled and refunded orders entirely.This kind of precise description โ calling out the specific recognition timing and exclusions โ is exactly the detail that prevents two different analysts from assuming slightly different things about what โrevenueโ includes, even once the calculation itself is centrally defined and consistent.
Common Mistakes
Defining a metricโs logic in the BI tool anyway, โjust for now.โ This immediately reintroduces the exact drift problem the metrics layer exists to prevent โ the discipline only pays off if metric logic actually lives in one declared place.
Not testing the underlying columns a metric depends on. A metric is only as reliable as the data feeding it โ an untested revenue column with silent data quality issues produces a confidently wrong metric, not a visibly broken one.
Redefining a derived metricโs components manually instead of referencing existing metrics. This defeats the reuse benefit โ always build derived metrics by referencing other declared metrics, not by re-expressing their underlying logic independently.
Summary
| Concept | Purpose |
|---|---|
| Metric definition (YAML) | Declares a business calculation exactly once |
| Derived metrics | Build new metrics from existing ones without re-deriving logic |
| Tool-agnostic querying | Any consumer gets the same definition, not a per-tool reimplementation |
A defined metrics layer turns โwhy do these two dashboards disagreeโ from a recurring investigation into a question with a structural answer: they canโt disagree, because theyโre both querying the same declared definition.