MetricFlow time spine
It's common in analytics engineering to have a date dimension or "time spine" table as a base table for different types of time-based joins and aggregations. The structure of this table is typically a base column of daily or hourly dates, with additional columns for other time grains, like fiscal quarters, defined based on the base column. You can join other tables to the time spine on the base column to calculate metrics like revenue at a point in time, or to aggregate to a specific time grain.
MetricFlow requires you to define at least one dbt model which provides a time-spine, and then specify (in YAML) the columns to be used for time-based joins. MetricFlow will join against the time-spine model for the following types of metrics and dimensions:
- Cumulative metrics
- Metric offsets
- Conversion metrics
- Slowly Changing Dimensions
- Metrics with the
join_to_timespine
configuration set to true
To see the generated SQL for the metric and dimension types that use time spine joins, refer to the respective documentation or add the compile=True
flag when querying the Semantic Layer to return the compiled SQL.
Configuring time spine in YAML
- The
models
key for the time spine must be in yourmodels/
directory. - Each time spine is a normal dbt model with extra configurations that tell dbt and MetricFlow how to use specific columns by defining their properties.
- You likely already have a calendar table in your project which you can use. If you don't, review the example time-spine tables for sample code.
- You add the configurations under the
time_spine
key for that model's properties, just as you would add a description or tests. - You only need to configure time-spine models that the Semantic Layer should recognize.
- At a minimum, define a time-spine table for a daily grain.
- You can optionally define additional time-spine tables for different granularities, like hourly. Review the granularity considerations when deciding which tables to create.
- If you're looking to specify the grain of a time dimension so that MetricFlow can transform the underlying column to the required granularity, refer to the Time granularity documentation
For example, given the following directory structure, you can create two time spine configurations, time_spine_hourly
and time_spine_daily
. MetricFlow supports granularities ranging from milliseconds to years. Refer to the Dimensions page (time_granularity tab) to find the full list of supported granularities.
Previously, you had to create a model called metricflow_time_spine
in your dbt project. Now, if your project already includes a date dimension or time spine table, you can simply configure MetricFlow to use that table by updating the model
setting in the Semantic Layer.
If you don’t have a date dimension table, you can still create one by using the following code snippet to build your time spine model.
For an example project, refer to our Jaffle shop example.
Now, break down the configuration above. It's pointing to a model called time_spine_daily
, and all the configuration is colocated with the rest of the model's properties. It sets the time spine configurations under the time_spine
key. The standard_granularity_column
is the lowest grain of the table, in this case, it's hourly. It needs to reference a column defined under the columns key, in this case, date_hour
. Use the standard_granularity_column
as the join key for the time spine table when joining tables in MetricFlow. Here, the granularity of the standard_granularity_column
is set at the column level, in this case, hour
.
Considerations when choosing which granularities to create
- MetricFlow will use the time spine with the largest compatible granularity for a given query to ensure the most efficient query possible. For example, if you have a time spine at a monthly grain, and query a dimension at a monthly grain, MetricFlow will use the monthly time spine. If you only have a daily time spine, MetricFlow will use the daily time spine and date_trunc to month.
- You can add a time spine for each granularity you intend to use if query efficiency is more important to you than configuration time, or storage constraints. For most engines, the query performance difference should be minimal and transforming your time spine to a coarser grain at query time shouldn't add significant overhead to your queries.
- We recommend having a time spine at the finest grain used in any of your dimensions to avoid unexpected errors. For example, if you have dimensions at an hourly grain, you should have a time spine at an hourly grain.
Example time spine tables
Daily
Daily (BigQuery)
Use this model if you're using BigQuery. BigQuery supports DATE()
instead of TO_DATE()
:
Hourly
{{
config(
materialized = 'table',
)
}}
with hours as (
{{
dbt.date_spine(
'hour',
"to_date('01/01/2000','mm/dd/yyyy')",
"to_date('01/01/2025','mm/dd/yyyy')"
)
}}
),
final as (
select cast(date_hour as timestamp) as date_hour
from hours
)
select * from final
-- filter the time spine to a specific range
where date_day > dateadd(year, -4, current_timestamp())
and date_hour < dateadd(day, 30, current_timestamp())