## TL;DR
Dynamic tables are Snowflake-native: you write a SELECT and Snowflake keeps the result fresh on a lag you choose, no scheduler needed. dbt incremental models are code-managed: dbt rebuilds or merges on your schedule with full tests, docs, and version control. Pick dynamic tables for simple always-fresh transforms inside Snowflake; pick dbt incremental when you need tests, complex logic, or a tool that works across warehouses.

## The query

```text
snowflake dynamic tables vs dbt incremental
```

## Use this when
- You need a transformed table in Snowflake that stays fresh with minimal plumbing
- A dbt incremental model feels like overkill for a simple SELECT-over-raw pattern
- You are deciding where freshness logic lives: warehouse-native or pipeline code

## Not for
- Warehouses other than Snowflake, dynamic tables are a Snowflake-only feature
- One-off or historical backfills, where a plain CTAS or dbt full refresh is simpler
- Models that need dbt tests, documentation, or cross-warehouse portability

## Steps

1. Write the dynamic table version. It is just a SELECT plus a target lag:

```sql
CREATE OR REPLACE DYNAMIC TABLE marts.orders_daily
  TARGET_LAG = '5 minutes'
  WAREHOUSE = transform_wh
AS
SELECT DATE_TRUNC('day', created_at) AS order_day,
       COUNT(*) AS orders,
       SUM(amount) AS revenue
FROM raw.orders
GROUP BY 1;
```
Expected output: the table is created and starts refreshing automatically. Snowflake tracks which source rows changed and incrementally updates the result; you never schedule anything.

2. Write the equivalent dbt incremental model for comparison:

```sql
{{ config(materialized='incremental', unique_key [your value] }}
SELECT DATE_TRUNC('day', created_at) AS order_day,
       COUNT(*) AS orders,
       SUM(amount) AS revenue
FROM {{ source('raw', 'orders') }}
{% if is_incremental() %}
WHERE created_at > (SELECT MAX(order_day) FROM {{ this }})
{% endif %}
GROUP BY 1
```
Expected output: same result set, but freshness depends on your dbt run schedule, and the merge logic is yours to maintain. The win is tests, docs, and the rest of the dbt toolchain.

3. Compare freshness honestly. Dynamic tables guarantee data no older than the target lag plus refresh time; dbt incremental is fresh as of the last successful run. If your dbt runs hourly and the business wants 5-minute freshness, dynamic tables win without a fight.
Expected output: a number per approach (lag vs schedule interval) you can put next to the requirement.

4. Compare cost. Dynamic tables bill for the background refresh warehouse; dbt incremental bills for your scheduled runs. For a simple aggregation, dynamic tables are usually cheaper because Snowflake only processes changed rows. For complex models with big merges, measure both for a week.
Expected output: warehouse credit usage per approach from ACCOUNT_USAGE. Dont guess; dynamic table refreshes show up as their own queries.

5. Check the logic limits. Dynamic tables support most SELECTs but not everything: no non-deterministic functions, no external tables in some cases, no DML inside. If your transform needs procedural steps, staging tables, or multi-step merges, dbt incremental handles it and dynamic tables dont.
Expected output: a yes/no per requirement. Complexity is the main reason teams keep dbt even when dynamic tables look simpler.

6. Decide with this split: dynamic tables for declarative always-fresh tables with simple SQL and tight lag requirements; dbt incremental for anything needing tests, docs, macros, cross-warehouse code, or orchestration alongside other tools. Many teams run both: dynamic tables for the hot path, dbt for the curated marts.
Expected output: a layered design instead of a religious war, with each table using the mechanism that fits its actual requirements.

## Variant phrasings

### snowflake dynamic tables limitations
Non-deterministic functions, certain joins with external tables, and procedural logic are restricted. The docs list the current set; it shrinks over time, so recheck before ruling them out.

### replace dbt incremental with dynamic tables
Works when the model is a plain SELECT with a unique key and no dbt-specific machinery. Keep dbt around for the tests and docs even if the compute moves.

### dynamic table target lag explained
TARGET_LAG is the maximum staleness Snowflake aims for: '5 minutes' means the table is never more than ~5 minutes behind, and Snowflake picks refresh timing to meet it. Shorter lag costs more.

## Why it matters
This is the most common Snowflake architecture question right now because the two tools overlap heavily and the wrong pick costs real money or real freshness. The deciding factors are almost never performance; they are tests, portability, and who owns the schedule. Name those explicitly and the choice is usually obvious.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_oqpYSdI4hLNvS_zamxzBZg
