10 dbt Macros You’ll Use in Every Project

14 minutes to read
Get free consultation

 

As a dbt project grows, a familiar pain point begins to surface: repetition. You find yourself writing the same CASE statements to handle nulls, the same casting logic to standardize data types, and the same complex calculations across multiple models. This redundant SQL not only slows down development but also introduces inconsistencies and creates a maintenance nightmare. What if a column name changes? You have to hunt down every instance and update it manually, hoping you don’t miss one.

This is where dbt macros come in. They are the solution to writing DRY (Don’t Repeat Yourself), maintainable, and scalable dbt code. Think of them as reusable functions that supercharge your SQL. At Stellans, we’ve found that a core set of macros is invaluable for accelerating our clients’ projects and ensuring long-term success. This article is your dbt macro starter kit. We’re sharing our curated toolkit of 10 essential, copy-paste-ready macros that will immediately add value to any dbt project, new or old.

What Are dbt Macros and Why Are They Essential?

In simple terms, dbt macros are reusable pieces of code that can be called throughout your dbt project. They function much like functions in a programming language like Python. You define a macro to perform a specific task, and then you can call it from any model, test, or even another macro, often passing in arguments to make it dynamic.

Under the hood, dbt uses a powerful templating engine called the Jinja templating language. When you run your dbt models, dbt first compiles your .sql files. This compilation step processes all the Jinja syntax, including your macro calls, and generates raw, executable SQL. This dynamic approach allows you to abstract away complexity and write cleaner, more powerful data models.

The Core Benefits: Standardization, Reusability, and Maintenance

Integrating macros into your workflow isn’t just a clever trick; it’s a fundamental practice for professional analytics engineering. The benefits are clear:

10 Practical dbt Macros for Your Starter Kit

Here is our go-to list of battle-tested macros. To use them, simply create a new .sql file in your project’s macros directory (e.g., macros/utils.sql) and paste the code.

Macro 1: Convert Cents to Dollars Safely

Use Case: Financial data often arrives as integers (cents) to avoid floating-point precision issues. This macro safely converts a column of cents into a properly formatted decimal dollar amount, preventing common casting errors.

The Code:

{% macro cents_to_dollars(column_name, precision=2) %}
    ({{ column_name }} / 100.0)::numeric(16, {{ precision }})
{% endmacro %}

How to Use It: In your model, you can call it directly within a SELECT statement.

SELECT
    order_id,
    {{ cents_to_dollars('amount_cents') }} AS amount_dollars
FROM {{ ref('stg_orders') }}

Macro 2: Coalesce Null Values

Use Case: Null values can wreak havoc on calculations and aggregations. This macro provides a clean, shorthand way to replace NULL with a specified default value, making your downstream queries more robust.

The Code:

{% macro coalesce_sql(column_name, default_value='0') %}
    coalesce({{ column_name }}, {{ default_value }})
{% endmacro %}

How to Use It: Replace nulls in a numeric column with 0 or in a string column with ‘N/A’.

SELECT
    user_id,
    {{ coalesce_sql('items_purchased', '0') }} AS items_purchased,
    {{ coalesce_sql('location', "'Unknown'") }} AS location
FROM {{ ref('stg_users') }}

Macro 3: Standardize Column Names

Use Case: Column names from different sources can be inconsistent (e.g., UserName, user_name, User Name). This macro standardizes them by trimming whitespace, converting to lowercase, and replacing spaces with underscores, which aligns with data modeling best practices.

The Code:

{% macro standardize_column_names(column_name) %}
    trim(lower(replace({{ column_name }}, ' ', '_')))
{% endmacro %}

How to Use It: Useful in staging models to clean up column names as soon as data enters your warehouse.

SELECT
    "User ID" AS {{ standardize_column_names('"User ID"') }},
    "First Name" AS {{ standardize_column_names('"First Name"') }}
FROM {{ source('raw_data', 'users') }}

Macro 4: Cross-Database Current Timestamp

Use Case: Different data warehouses have slightly different functions for getting the current timestamp (e.g., GETDATE() in SQL Server, CURRENT_TIMESTAMP() in PostgreSQL/Snowflake). This macro abstracts that difference, making your models more portable across different databases.

The Code:

{% macro get_current_timestamp() %}
    {{ adapter.dispatch('get_current_timestamp', 'dbt_utils')() }}
{% endmacro %}

{% macro default__get_current_timestamp() %}
    current_timestamp
{% endmacro %}

{% macro sqlserver__get_current_timestamp() %}
    getdate()
{% endmacro %}

Note: This pattern uses adapter.dispatch and is inspired by the dbt-utils package.

How to Use It: Add a loaded_at column to your models to track when the data was transformed.

SELECT
    *,
    {{ get_current_timestamp() }} as loaded_at
FROM {{ ref('stg_sessions') }}

Macro 5: Cross-Database Date Part Extraction

Use Case: Similar to timestamps, the syntax for extracting parts of a date (like year, month, or day) can vary between SQL dialects. This macro provides a consistent interface for these common operations.

The Code:

{% macro date_part(part, date_column) %}
    date_part('{{ part }}', {{ date_column }})
{% endmacro %}

{% macro bigquery__date_part(part, date_column) %}
    extract({{ part }} from {{ date_column }})
{% endmacro %}

How to Use It: Easily extract features from a date column for analysis.

SELECT
    order_date,
    {{ date_part('year', 'order_date') }} AS order_year,
    {{ date_part('month', 'order_date') }} AS order_month
FROM {{ ref('stg_orders') }}

Macro 6: Generate Custom Schema Names

Use Case: When working with multiple environments (dev, prod), you often want dbt to build models in different schemas. This powerful macro allows you to dynamically generate schema names, for instance, by appending your target name (e.g., prod_marts, dev_marts).

The Code:

{% macro generate_schema_name(custom_schema_name, node) -%}
    {%- set default_schema = target.schema -%}
    {%- if custom_schema_name is none -%}
        {{ default_schema }}
    {%- else -%}
        {{ custom_schema_name | trim }}
    {%- endif -%}
{%- endmacro %}

Note: This macro overrides dbt’s default behavior. It should be placed in its own file like macros/get_custom_schema.sql and configured in your dbt_project.yml.

How to Use It: In dbt_project.yml, you configure models to use this logic.

models:
  my_project:
    marts:
      +schema: "{{ generate_schema_name(target.name ~ '_marts', node) }}"

Macro 7: Generate a Surrogate Key

Use Case: A cornerstone of dimensional modeling is the surrogate key, a unique identifier for a row in a dimension table. This macro generates a consistent, hashed surrogate key from one or more business keys, ensuring uniqueness and decoupling from source system IDs. It’s one of the most useful macros from the dbt-utils package.

The Code:

{% macro surrogate_key(column_list) %}
    {{ dbt_utils.surrogate_key(column_list) }}
{% endmacro %}

Note: This requires you to have the dbt-utils package installed in your packages.yml file.

How to Use It: Create a primary key for your dimension or fact tables.

SELECT
    {{ surrogate_key(['user_id', 'session_id']) }} AS user_session_pk,
    user_id,
    session_id
FROM {{ ref('stg_sessions') }}

Macro 8: Audit Helper to Compare Relations

Use Case: How do you verify that a model refactoring didn’t change the underlying data? This audit helper macro compares two relations (tables/views) and returns the rows that don’t match, making it perfect for regression testing.

The Code:

{% macro audit_helper_compare_relations(a_relation, b_relation, primary_key) %}
  {{ dbt_utils.compare_relations(
      a_relation=a_relation,
      b_relation=b_relation,
      primary_key=primary_key
  ) }}
{% endmacro %}

Note: This also comes from the dbt-utils package and is incredibly powerful for testing.

How to Use It: Run it as an operation to compare your new model (dev_dim_users) against the production version.

dbt run-operation audit_helper_compare_relations --args '{a_relation: ref("dim_users"), b_relation: ref("dev_dim_users"), primary_key: "user_id"}'

Macro 9: Grant Select Permissions on a Schema

Use Case: Data governance is critical. After your models are built, you need to grant usage rights to your BI tools or downstream consumers. This macro, when used as a post-hook, automates the process of granting SELECT privileges on all tables within a schema to a specific role.

The Code:

{% macro grant_select(schema, role) %}
  {% set sql %}
    grant usage on schema {{ schema }} to role {{ role }};
    grant select on all tables in schema {{ schema }} to role {{ role }};
    grant select on all views in schema {{ schema }} to role {{ role }};
  {% endset %}

  {{ run_query(sql) }}
  {{ log('Granted select on all tables in ' ~ schema ~ ' to ' ~ role, info=True) }}
{% endmacro %}

How to Use It: In your dbt_project.yml, add this as a post-hook to run after your models are built.

on-run-end:
  - "{{ grant_select('marts', 'reporter_role') }}"

Macro 10: Check if a Date is a Weekend

Use Case: For business analytics, it’s often useful to distinguish between weekdays and weekends. This simple macro returns a boolean value, making it easy to flag weekend activity for reporting or analysis.

The Code:

{% macro is_weekend(date_column) %}
    -- Assumes Sunday=0, Saturday=6. Adjust for your warehouse if needed.
    extract(dayofweek from {{ date_column }}) in (0, 6)
{% endmacro %}

How to Use It: Create a boolean column in a dimension table.

SELECT
    *,
    {{ is_weekend('order_date') }} as is_weekend_order
FROM {{ ref('dim_orders') }}

Best Practices for Writing and Maintaining Your Macros

Now that you have your starter kit, keeping your macros organized and effective is the next step. Following a few best practices will ensure they remain a valuable asset as your project scales.

Keep Them Simple and Focused

A macro should do one thing and do it well. This means creating small, focused macros instead of monolithic ones that handle multiple, unrelated tasks. A small, focused macro like cents_to_dollars is easier to understand, test, and debug than one that tries to handle currency conversion, nulls, and standardization all at once.

Document Everything

Your future self (and your teammates) will thank you. Use comments within the macro’s code to explain what it does, what its parameters are, and any assumptions it makes. For more complex logic, consider adding a README.md file in your macros directory that provides a comprehensive overview of your custom macros.

Test Your Macros

You can test a macro’s compiled output without running a full model by using the dbt run-operation command. This is a great way to quickly iterate and ensure your macro generates the correct SQL. Simply call your macro from another macro and use the log function to print the result to the command line.

Leverage Community Packages

Before you write a macro from scratch, check if it already exists in a community package like dbt-utils. This package contains dozens of battle-tested macros that solve the most common problems in analytics engineering. Take advantage of robust, community-vetted solutions whenever they are available. For more details, refer to dbt’s official documentation.

Scale Your Analytics with Expert dbt Support

Implementing these macros is a great first step toward building a more professional and efficient dbt workflow. However, as your team and data complexity grow, scaling dbt across a large organization requires a more strategic approach to governance, performance, and analytics engineering best practices.

This is where a dedicated partner can make all the difference. Stellans’ dbt Development Support is designed to help you move beyond the basics. We work with your team to establish robust data modeling frameworks, optimize your dbt project for performance and cost, and build a truly scalable data platform. We help you connect your data transformation strategies to real business outcomes, turning your data team into a driver of growth.

Ready to build a truly scalable and maintainable dbt practice? Contact Stellans today to learn how our experts can help.

Conclusion

dbt macros are a fundamental tool for any serious analytics engineer. They are the key to transforming your dbt project from a collection of individual SQL queries into a cohesive, maintainable, and scalable data asset. By abstracting away repetitive logic, you not only save time but also improve the reliability and consistency of your data transformations.

The 10 macros provided here are a powerful starting point. They address some of the most common, recurring challenges faced in data modeling. We encourage you to integrate them into your projects and start experiencing the benefits of writing DRYer, cleaner code today.

FAQs

What are dbt macros used for?

dbt macros are used to write reusable, modular SQL code. They help analytics engineers standardize logic, reduce code duplication, and manage complex transformations more efficiently across their projects, acting like functions for your SQL.

How do you create a dbt macro?

You create a dbt macro by defining it within a .sql file in the macros directory of your dbt project. The syntax uses Jinja templating, starting with {% macro macro_name(arguments) %} and ending with {% endmacro %}.

How do you reuse dbt macros across projects?

dbt macros can be reused across projects by creating a dbt package. By packaging your common macros, you can install that package as a dependency in any other dbt project, making your macro toolkit portable and easy to maintain.

Article By:

https://stellans.io/wp-content/uploads/2024/06/IMG_5527-2-1.png
Vitaly Lilich

Co-founder and CEO of Stellans

Related Posts

    Get a Free Data Audit

    * You can attach up to 3 files, each up to 3MB, in doc, docx, pdf, ppt, or pptx format.