Analytics Engineering in SQL
Turn SQL into reviewed, tested, versioned code
Analytics engineering treats SQL models as code: versioned, tested, documented and layered — the discipline dbt makes real and that makes warehouse models reliable.
What you will learn
- Structure warehouse SQL into layered models
- Add tests (uniqueness, not-null, freshness) as gates
- Use macros/configuration to keep models DRY and portable
What is this?
Analytics engineering is applying software engineering to SQL: models are code in a repo, structured in layers (staging → marts), compiled and run by tools like dbt, tested with assertions, and documented so analysts consume a dependable semantic surface instead of raw table soup.
Why do you need it?
- Ad-hoc 'gold' SQL that only one person understands becomes the single point of truth.
- Versioned, reviewed models make warehouse changes as safe as code changes.
- Tests gate publishes: a model with duplicate keys fails before a dashboard trusts it.
Mental model
A re:published, reviewed library
A modeling codebase in dbt
Every book (model) has an ISBN (lineage), a reviewer (code review), an edition (version), and shelf checks (tests). Readers (analysts) trust the catalog because the library maintains it — not because they re-read every page.
How it works
- Layers: staging (raw → clean), marts (clean → business metrics); each with an owner and grain.
- dbt compiles Jinja-SQL models, runs them in dependency order, and materializes views/tables/increments.
- Tests: uniqueness, not-null, accepted values, and custom SQL assertions run as part of build.
- Documentation: each model/column gets docs rendered into a catalog.
- Macros and packages centralize repeated SQL so dialect and business logic stay consistent.
dbt-style layered modeling.
Examples
A tested staging model
-- stg_orders.sqlSELECT id AS order_id, customer_id::BIGINT AS customer_id, COALESCE(amount, 0) AS amount, created_at::TIMESTAMPTZ AS created_atFROM {{ source('oltp', 'orders') }}WHERE id IS NOT NULL; -- tests: source uniqueness (one row per order_id),-- not-null on customer_id, freshness on max(created_at)The model defines clean contract at a grain; tests enforce uniqueness, null-safety and freshness.
In production
- A finance mart reruns nightly; a duplicate-key test fails the build and pages the owner before dashboards move.
- A schema change to a source breaks the staging model's contract test — surfaced in CI, not at 6am.
- Analysts query certified marts; lineage shows every dashboard back to source rows.
Best practices
- Keep staging narrow (cleaning only) and marts business-flavored at an explicit grain.
- Test every model's primary key, not-null on criticals, and freshness thresholds.
- Review SQL in PRs like application code; a model change is a schema change.
- Document columns with owner intent: 'amount is net of tax, in EUR'.
Common mistakes
- Letting analysts query staging directly, recreating the 'spaghetti' analytics it was meant to end.
- Materializing every model as a table — views for interfaces, tables for hot marts only.
- Skipping tests on the documented contract, so 'certified' tables drift.
- One model with 8 joins doing everything — split by grain and test each.
Common issues
Test failed: duplicate rows in stg_ordersSource gained multiple rows per natural key or the model's grain drifted
Document the intended grain, dedup explicitly, and update the uniqueness test.
Model compiled locally but fails in validationProfiles/credentials differ between environments
Standardize the target schema/profile and run CI against the same warehouse.
Debugging tips
- When a downstream mart changes, use lineage to diff its staging parents and the commit.
- Re-run a single model + its dependents rather than the whole build.
- Read the compiled SQL (target/compiled) when a model misbehaves — the macro expansion is the truth.
Interview checkpoint
Quick quiz
Which job does a dbt-style test (uniqueness on order_id) perform?
In layered modeling, marts should be re-derived directly from raw sources.