--- name: dbt_sql_style description: "General SQL style guide for writing dbt models: CTE structure, formatting conventions, keywords, joins, CASE WHEN, and aggregations. Reusable across any dbt project." --- # SQL Style Guide for dbt Models Apply these conventions to all SQL written in dbt models. Consistent style improves readability and code review speed. --- ## Core Rules | Rule | Convention | |------|-----------| | Keywords | **lowercase** (`select`, `from`, `where`, `join`, `group by`, `order by`) | | Indentation | **4 spaces** — no tabs | | Commas | **Trailing** (end of line), not leading | | One column per line | Always — no inline column lists | | Final SELECT | `select * from ` | --- ## CTE Structure Always use CTEs instead of subqueries. Name each CTE after the source or concept it represents. ```sql with source as ( select * from {{ source('system', 'table') }} ), renamed as ( select ---------- ids id_column, ---------- strings name_column as readable_name, ---------- dates date_column as event_date, ---------- numerics amount_column as total_usd from source ) select * from renamed ``` - Blank line after the opening `(` and before the closing `)` of each CTE. - Blank line between each CTE block. --- ## Joins ```sql from orders left join customers on orders.customer_id = customers.id left join products on orders.product_id = products.id ``` - **Always use `left join`** unless you have a specific reason for inner/cross. - Never use bare `join` — always be explicit. - **Always qualify columns** with table/CTE alias when joining multiple sources. - Place `on` conditions on the next line, indented 4 spaces. --- ## CASE WHEN ```sql case when status = 'cancelled' and cancelled_date is not null then cancelled_date when status = 'expired' then expiration_date else created_date end as effective_date, ``` - One `when` condition per line. - `then` indented 4 spaces under `when`. - Always include `else`. - Close with `end as `. --- ## Aggregations ```sql select region, order_type, count(*) as order_count, sum(net_value) as total_net_value_usd from orders group by 1, 2 ``` - Use **positional references** in `group by` (e.g., `group by 1, 2`). - Name aggregated columns clearly: `sum(x) as total_x`. --- ## Column Naming Conventions | Type | Pattern | Example | |------|---------|---------| | Boolean | `is_*` / `has_*` | `is_cancelled`, `has_attachment` | | Date | `*_date` | `created_date`, `shipped_date` | | Timestamp | `*_at` | `created_at`, `updated_at` | | Numeric amount | `*_` | `total_usd`, `quantity_ea` | | Foreign key | `_id` | `customer_id`, `order_id` |