Pipelines land data into tables you define. Get the definition right once; every downstream query inherits it.
CREATE TABLE stg_orders (
order_id BIGINT PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES dim_customers (customer_id),
ordered_at TIMESTAMPTZ NOT NULL DEFAULT now(),
amount_cents INTEGER NOT NULL CHECK (amount_cents >= 0),
status TEXT NOT NULL DEFAULT 'new'
);
Rules that prevent 3 a.m. pages
- Right-sized types:
BIGINTfor ids (INT overflows),NUMERICfor money math,TIMESTAMPTZfor event time.TEXToverVARCHAR(n)unless a real limit exists. - Constraints are documentation that executes:
NOT NULL,CHECK,PRIMARY KEY,REFERENCES. A load that violates them fails fast at the boundary instead of silently poisoning marts. IF NOT EXISTSin pipeline DDL so re-runs are safe:CREATE TABLE IF NOT EXISTS stg_orders (...);- Staging vs mart separation: land raw in
stg_*(loose types allowed), enforce strict types in modeled marts. Never let analysts query staging.
Warehouse note
BigQuery/Snowflake skip PK/FK enforcement โ declare them anyway (optimizers
and dbt contracts read them), and enforce uniqueness with a post-load
QUALIFY ROW_NUMBER() check instead.