Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when writing or changing analytical SQL or a dbt model — CTE structure, window functions, grain and join correctness, dbt data tests, unit tests, contracts and incremental models, and how to run them
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Analytical SQL fails quietly: a join that fans out, a NULL that drops rows from a NOT IN, a running total over the wrong frame. The query runs, the dashboard shows a number, the number is wrong. dbt gives you the tools to make SQL testable — use them the way you use pytest.
Core principle: every model declares its grain and tests it; logic is proven with a dbt unit test before it is trusted with a real table.
Read dbt_project.yml, packages.yml (is dbt_utils installed?), the adapter in profiles.yml or CI (Postgres, Snowflake, BigQuery, Databricks, DuckDB), any .sqlfluff config, and the existing folder and naming scheme (staging/stg_<source>__<entity>, intermediate/, marts/fct_*, dim_*). Follow them. SQLMesh projects follow SQLMesh's own model and audit conventions — never mix the two.
with
orders as (
select * from {{ ref('stg_shop__orders') }}
),
order_lines as (
select * from {{ ref('stg_shop__order_lines') }}
),
line_totals as (
select
order_id,
sum(amount) as order_value,
count(*) as line_count
from order_lines
where status != 'cancelled'
group by order_id
),
final as (
select
orders.order_id,
orders.customer_id,
orders.ordered_at,
coalesce(line_totals.order_value, 0) as order_value,
coalesce(line_totals.line_count, 0) as line_count
from orders
left join line_totals using (order_id)
)
select * from final
ref() and source() only — never a hard-coded schema.table.line_totals), so the join stays one-to-one.-- latest row per key, deterministic tie-break
select *
from events
qualify row_number() over (partition by event_id order by loaded_at desc, _row_id desc) = 1
-- running total: say ROWS, or ties on order_date are summed together
sum(amount) over (
partition by customer_id
order by order_date, order_id
rows between unbounded preceding and current row
) as running_spend
-- previous value
lag(order_date) over (partition by customer_id order by order_date, order_id) as previous_order_date
QUALIFY exists in Snowflake, BigQuery, Databricks and DuckDB; elsewhere filter row_number() = 1 in an outer query. A window ORDER BY without a unique tie-breaker gives a different "latest row" on each run.
| Pitfall | Fix |
|---|---|
x not in (select y ...) where y can be NULL returns no rows | not exists (...) |
Integer division (1 / 2 = 0 in Postgres, Redshift, SQL Server) | cast one side to numeric/float |
| Division by zero | x / nullif(y, 0) |
between on timestamps includes the end instant | half-open: >= start and < end |
Filter on the right table of a LEFT JOIN in WHERE turns it into an inner join | move the condition into ON |
count(col) skips NULLs, count(*) does not | choose deliberately; count(distinct key) for entities through joins |
select distinct masking a fan-out | fix the join grain instead |
| Date truncation in local time vs UTC | convert to one timezone first, state which |
| Average of per-group averages | aggregate from rows, or weight by group size |
union silently deduplicating | union all unless dedup is intended |
models:
- name: fct_orders
config:
contract:
enforced: true
columns:
- name: order_id
data_type: varchar
constraints:
- type: not_null
data_tests:
- unique
- not_null
- name: customer_id
data_type: varchar
data_tests:
- relationships:
to: ref('dim_customers')
field: customer_id
- name: order_value
data_type: numeric
unique + not_null on its key. A composite key uses dbt_utils.unique_combination_of_columns when the repo has dbt_utils, otherwise a surrogate key column with the two tests.arguments: — write tests in the form the repository's YAML already uses.unit_tests:
- name: fct_orders_excludes_cancelled_lines
model: fct_orders
given:
- input: ref('stg_shop__orders')
rows:
- {order_id: o1, customer_id: c1, ordered_at: '2026-01-05'}
- {order_id: o2, customer_id: c1, ordered_at: '2026-01-06'}
- input: ref('stg_shop__order_lines')
rows:
- {order_id: o1, amount: 10, status: ok}
- {order_id: o1, amount: 5, status: cancelled}
expect:
rows:
- {order_id: o1, order_value: 10, line_count: 1}
- {order_id: o2, order_value: 0, line_count: 0}
Write the unit test first, watch dbt test --select fct_orders fail, then change the SQL. Unit tests need the model's parents to exist as relations; dbt run --select +fct_orders --empty creates them without data in the dev target.
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='merge',
on_schema_change='append_new_columns'
) }}
select ...
from {{ ref('stg_shop__orders') }}
{% if is_incremental() %}
where updated_at > (select {{ dbt.dateadd('day', -3, 'max(updated_at)') }} from {{ this }})
{% endif %}
unique_key so re-processed rows update instead of duplicating; a lookback window for late-arriving rows.unique test still passes; a --full-refresh build and an incremental build agree on row counts for the same period.before_deploy / after_deploy (rule deploy-runbook-fields).| Goal | Command |
|---|---|
| Parse without a warehouse | dbt parse |
| See the compiled SQL | dbt compile --select fct_orders (then read target/compiled/...) |
| Look at rows | dbt show --select fct_orders --limit 20 |
| Build + test what changed and its children | dbt build --select state:modified+ --state <prod artifacts> (only when the repo keeps state artifacts) |
| Otherwise | dbt build --select fct_orders+ against the dev target |
Credentials come from env_var() in profiles.yml, never from the repo. With no warehouse access here, run what you can (dbt parse, a DuckDB target if the project has one) and say exactly which tests did not run.
Bind parameters — never format values into SQL strings:
# ❌ injection and quoting bugs
pd.read_sql(f"select * from orders where country = '{country}'", engine)
# ✅
pd.read_sql(text("select * from orders where country = :country"), engine, params={"country": country})
distinct.severity: warn without a reason.select * from a source in a mart.아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.