125 lines
2.7 KiB
Markdown
125 lines
2.7 KiB
Markdown
---
|
|
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 <last_cte_name>` |
|
|
|
|
---
|
|
|
|
## 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 <alias>`.
|
|
|
|
---
|
|
|
|
## 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 | `*_<unit>` | `total_usd`, `quantity_ea` |
|
|
| Foreign key | `<entity>_id` | `customer_id`, `order_id` |
|