Data Engineering  /  dbt

๐Ÿ”„ dbt โ€” Data Build Tool 60 guides ยท updated 2026

Analytics engineering with SQL โ€” models, tests, sources, and Jinja macros that turn raw warehouse tables into trustworthy, documented data products.

dbt_utils Package: The Macros Every dbt Project Ends Up Needing

If youโ€™ve written more than a handful of dbt models, youโ€™ve probably already hit one of the problems dbt_utils solves โ€” generating a surrogate key from multiple columns, building a complete date range with no gaps, or unioning several similarly-shaped tables together. dbt_utils is the most widely installed community package in the dbt ecosystem precisely because these problems are so common that reinventing them per-project is wasted effort.


Installing dbt_utils

packages.yml
packages:
- package: dbt-labs/dbt_utils
version: [">=1.1.0", "<2.0.0"]
Terminal window
dbt deps

Once installed, every macro is called with the dbt_utils. prefix, as covered generally in dbt Packages.


generate_surrogate_key: Composite Keys Done Correctly

Many source tables donโ€™t have a clean single-column primary key โ€” an events table might only be uniquely identified by the combination of user_id, event_type, and timestamp. Hand-writing a hash of concatenated columns is error-prone (null handling, type casting, delimiter collisions). generate_surrogate_key handles all of it.

select
{{ dbt_utils.generate_surrogate_key(['user_id', 'event_type', 'created_at']) }} as event_key,
user_id,
event_type,
created_at
from {{ ref('stg_events') }}

This produces a consistent hash, correctly handling nulls (which would otherwise silently break naive string concatenation), across any warehouse dbt supports โ€” the exact SQL generated differs by warehouse dialect, but the macro abstracts that difference away entirely.


date_spine: Filling In Missing Dates

A revenue-by-day report with gaps on days with zero orders is a subtly wrong report โ€” a chart with missing data points instead of visible zeros. date_spine generates a complete, gapless sequence of dates you can left-join your actual data onto.

with date_range as (
{{ dbt_utils.date_spine(
datepart="day",
start_date="cast('2024-01-01' as date)",
end_date="cast(current_date as date)"
) }}
)
select
date_range.date_day,
coalesce(sum(orders.amount), 0) as daily_revenue
from date_range
left join {{ ref('stg_orders') }} as orders
on date_range.date_day = orders.order_date
group by 1
order by 1

Every day in the range appears in the output, whether or not any orders happened that day โ€” the difference between a report that shows โ€œ$0 on March 3rdโ€ and one that simply omits March 3rd entirely and lets a reader draw the wrong conclusion.


union_relations: Combining Similarly-Shaped Tables

Manually writing union all across a dozen regional or sharded tables, and keeping column lists in sync as schemas evolve, gets tedious fast. union_relations handles column alignment automatically.

{{ dbt_utils.union_relations(
relations=[
ref('stg_orders_us'),
ref('stg_orders_eu'),
ref('stg_orders_apac')
]
) }}

If one relation is missing a column the others have, union_relations fills it with null rather than failing the whole union โ€” a meaningfully safer default than a hand-written union all that breaks the moment schemas drift slightly out of sync.


Generic Test Macros: not_null_proportion, equal_rowcount, and More

dbt_utils isnโ€™t only about data-generation macros โ€” it also ships generic tests beyond dbtโ€™s built-in set, usable exactly like unique or not_null in a YAML file.

models:
- name: stg_orders
columns:
- name: discount_pct
tests:
- dbt_utils.not_null_proportion:
at_least: 0.95

This test passes as long as at least 95% of rows have a non-null discount_pct, which is far more realistic for many real-world columns than a strict not_null test that fails the entire build over a small, expected percentage of missing values.


Other Frequently-Used Macros Worth Knowing

MacroWhat it does
dbt_utils.star()Selects all columns except a specified exclusion list
dbt_utils.pivot()Pivots row values into columns without hand-writing every case statement
dbt_utils.get_column_values()Returns distinct values of a column, usable to drive dynamic Jinja loops
dbt_utils.date_trunc()Cross-warehouse-compatible date truncation
-- star() with exclusions โ€” useful for staging models cleaning raw tables
select
{{ dbt_utils.star(from=source('raw', 'orders'), except=["internal_notes", "_raw_metadata"]) }}
from {{ source('raw', 'orders') }}

dbt_expectations: A Complementary Package Worth Knowing

For teams whose data quality needs go beyond dbt_utilsโ€™s testing macros, dbt_expectations (inspired by the Python library Great Expectations) adds a much larger library of statistical and distribution-based tests.

columns:
- name: order_amount
tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
max_value: 100000
- dbt_expectations.expect_column_mean_to_be_between:
min_value: 10
max_value: 500

These go further than dbt_utilsโ€™s simpler checks โ€” verifying not just that values fall in a range, but that a columnโ€™s statistical properties (mean, standard deviation) stay within expected bounds run over run, which is useful for catching subtler data drift that a basic range check wouldnโ€™t flag.

When Not to Reach for dbt_utils

Not every problem benefits from a generic macro. If your logic is genuinely specific to your business rules โ€” a custom churn definition, a company-specific revenue recognition rule โ€” writing your own macro or plain SQL is more transparent than forcing it through a generic tool that wasnโ€™t designed for it. dbt_utils earns its place solving genuinely generic, cross-project problems; it isnโ€™t a replacement for understanding your own data.


Common Mistakes

Not pinning the version. dbt_utils has had breaking changes across major versions โ€” an unpinned install can silently change macro behavior on a routine dbt deps run, exactly as described in dbt Packages.

Using union_relations without checking column type compatibility. It aligns column names, but a column thatโ€™s an integer in one relation and a string in another will still cause a type error โ€” the macro solves missing columns, not type mismatches.

Forgetting dbt_utils. as case-sensitive namespace prefix. A macro call without the correct package prefix simply wonโ€™t be found, producing a compilation error that looks like the macro doesnโ€™t exist at all.

Summary

MacroSolves
generate_surrogate_keyComposite/hashed primary keys with correct null handling
date_spineGapless date ranges for accurate time-series reporting
union_relationsSafely unioning tables with slightly different schemas
not_null_proportionRealistic null-tolerance testing beyond strict not_null

dbt_utils is close to a standard library for dbt projects โ€” before writing a macro for a problem that feels generic, itโ€™s worth five minutes checking whether this package already solved it, tested against a far larger set of edge cases than any single projectโ€™s macro would be.