Writing the same expression for every status, country or month is repetitive and easy to get wrong. A Jinja for loop writes it for you: you keep a list, dbt generates one line per value. The typical case is a pivot, one column per value of a category.

Copy and paste

A fixed list:

-- models/marts/orders_by_status.sql
{%- set statuses = ['placed', 'shipped', 'returned'] -%}

select
    customer_id,
    {%- for status in statuses %}
    sum(case when status = '{{ status }}' then amount else 0 end) as {{ status }}_amount
    {%- if not loop.last %},{% endif %}
    {%- endfor %}
from {{ ref('stg_orders') }}
group by customer_id

What dbt compiles:

select
    customer_id,
    sum(case when status = 'placed' then amount else 0 end) as placed_amount,
    sum(case when status = 'shipped' then amount else 0 end) as shipped_amount,
    sum(case when status = 'returned' then amount else 0 end) as returned_amount
from "shop"."main"."stg_orders"
group by customer_id

The values read from the data, with dbt_utils:

{%- set statuses = dbt_utils.get_column_values(ref('stg_orders'), 'status') -%}

or with your own macro, as in how to write your first macro:

{%- set statuses = get_statuses() -%}

The rest of the model stays the same.

How the loop works

A list from the data

A fixed list must be updated when a new status appears. A list read from the table follows the data, at a price:

A fixed list is often the better choice for values that rarely change. When in doubt, add an accepted_values test with the same list, so you hear about new values.

Other loops

Tested with dbt Core 1.12 and dbt_utils 1.4.1.

References