Dynamic Tables¶
π Dynamic Tables - Dynamic table documentation
Overview¶
This document covers dynamic tables in Snowflake, including architecture, target lag configuration, pipeline chaining, refresh mechanics, and comparison with alternative approaches. Dynamic tables represent Snowflake's declarative approach to data pipelines, replacing imperative task/stream patterns for many common transformation use cases.
Architecture¶
How Dynamic Tables Work¶
- Defined by a SQL query that specifies the desired result
- Snowflake automatically determines how to refresh the result
- Incremental refresh when possible, full refresh when needed
- Target lag specifies maximum acceptable data staleness
- Snowflake scheduler manages refresh timing automatically
π Dynamic Table Concepts - Core concepts
Target Lag¶
- Defines the maximum time the dynamic table can lag behind source data
- Snowflake schedules refreshes to meet the target lag
- Shorter target lag = more frequent refresh = higher cost
- Longer target lag = less frequent refresh = lower cost
- Minimum target lag: 1 minute (downstream) or specific time value
-- Target lag as time interval
CREATE DYNAMIC TABLE hourly_summary
TARGET_LAG = '1 hour'
WAREHOUSE = transform_wh
AS
SELECT DATE_TRUNC('hour', event_time) AS hour,
event_type,
COUNT(*) AS event_count
FROM raw_events
GROUP BY 1, 2;
-- Target lag as DOWNSTREAM (matches consumers)
CREATE DYNAMIC TABLE intermediate_table
TARGET_LAG = DOWNSTREAM
WAREHOUSE = transform_wh
AS
SELECT * FROM source_table WHERE is_valid = TRUE;
DOWNSTREAM Target Lag¶
- Dynamic table refreshes only when a downstream dynamic table needs it
- Useful for intermediate pipeline stages
- Refresh frequency determined by the downstream consumer's target lag
- Avoids unnecessary refreshes when no consumer needs fresh data
- Cannot be set on the final (leaf) dynamic table in a chain
Pipeline Chaining¶
Multi-Stage Pipelines¶
-- Stage 1: Cleansing (triggered by downstream)
CREATE DYNAMIC TABLE clean_orders
TARGET_LAG = DOWNSTREAM
WAREHOUSE = transform_wh
AS
SELECT order_id,
TRIM(customer_name) AS customer_name,
ABS(order_amount) AS order_amount,
TO_DATE(order_date_str, 'YYYY-MM-DD') AS order_date
FROM raw_orders
WHERE order_id IS NOT NULL
AND order_amount > 0;
-- Stage 2: Enrichment with joins (triggered by downstream)
CREATE DYNAMIC TABLE enriched_orders
TARGET_LAG = DOWNSTREAM
WAREHOUSE = transform_wh
AS
SELECT o.order_id,
o.customer_name,
o.order_amount,
o.order_date,
c.customer_segment,
c.region,
p.product_category
FROM clean_orders o
JOIN dim_customers c ON o.customer_name = c.customer_name
JOIN dim_products p ON o.product_id = p.product_id;
-- Stage 3: Aggregation (sets the refresh cadence)
CREATE DYNAMIC TABLE daily_sales_summary
TARGET_LAG = '30 minutes'
WAREHOUSE = transform_wh
AS
SELECT order_date,
region,
product_category,
customer_segment,
COUNT(*) AS order_count,
SUM(order_amount) AS total_revenue,
AVG(order_amount) AS avg_order_value
FROM enriched_orders
GROUP BY 1, 2, 3, 4;
Pipeline DAG Visualization¶
raw_orders (base table)
|
v
clean_orders (DOWNSTREAM lag)
|
v
enriched_orders (DOWNSTREAM lag) <-- dim_customers, dim_products
|
v
daily_sales_summary (30 minutes lag) <-- drives refresh of entire chain
Refresh Mechanics¶
Incremental vs Full Refresh¶
- Snowflake automatically determines the optimal refresh strategy
- Incremental: Only processes changed data since last refresh
- Full: Recomputes entire result set from scratch
- Incremental is preferred but not always possible
Operations That Support Incremental Refresh¶
| Operation | Incremental Support |
|---|---|
| SELECT with filters | Yes |
| JOIN (inner, left, right) | Yes |
| GROUP BY / aggregation | Yes |
| UNION ALL | Yes |
| Window functions | Depends on complexity |
| UNION (with dedup) | Full refresh required |
| Non-deterministic functions | Full refresh required |
| Subqueries in WHERE | Depends on complexity |
π Incremental Refresh - Refresh mechanics
Monitoring Refresh¶
-- Refresh history
SELECT name, refresh_version, refresh_action,
refresh_trigger, state,
data_timestamp, refresh_start_time, refresh_end_time,
DATEDIFF('second', refresh_start_time, refresh_end_time) AS duration_sec
FROM TABLE(INFORMATION_SCHEMA.DYNAMIC_TABLE_REFRESH_HISTORY(
NAME => 'daily_sales_summary',
DATA_TIMESTAMP_START => DATEADD('day', -1, CURRENT_TIMESTAMP())
))
ORDER BY refresh_start_time DESC;
-- Dynamic table graph (dependency visualization)
SELECT name, target_lag, refresh_mode, scheduling_state
FROM TABLE(INFORMATION_SCHEMA.DYNAMIC_TABLE_GRAPH_HISTORY());
Refresh States¶
| State | Meaning |
|---|---|
| RUNNING | Refresh in progress |
| SUCCEEDED | Refresh completed successfully |
| FAILED | Refresh encountered an error |
| UPSTREAM_FAILED | Upstream dynamic table refresh failed |
| SKIPPED | Refresh skipped (no source changes) |
Dynamic Tables vs Alternatives¶
vs Tasks + Streams¶
| Aspect | Dynamic Tables | Tasks + Streams |
|---|---|---|
| Definition style | Declarative (what) | Imperative (how) |
| Scheduling | Automatic (target lag) | Manual (CRON/interval) |
| Incremental logic | Automatic | Manual (stream consumption) |
| Error handling | Automatic retry | Custom logic needed |
| Complex DML | Limited (INSERT/SELECT) | Full (MERGE, multi-statement) |
| External calls | Not supported | Supported (stored procedures) |
| Debugging | Less transparent | More control and visibility |
| Maintenance | Lower | Higher |
When to Use Dynamic Tables¶
- Standard SQL transformations (SELECT, JOIN, GROUP BY)
- Pipeline freshness defined by time-based SLA
- Desire for minimal pipeline maintenance
- No need for complex DML (MERGE, DELETE)
- No external API calls or side effects needed
When to Use Tasks + Streams¶
- Complex CDC with MERGE operations
- Multi-statement transaction logic
- Integration with external systems
- Custom error handling and retry logic
- Non-SQL transformations (Snowpark stored procedures)
- Conditional execution based on complex criteria
vs Materialized Views¶
| Aspect | Dynamic Tables | Materialized Views |
|---|---|---|
| Joins | Supported | Not supported (single table) |
| Complex SQL | Full SQL | Limited SQL |
| Refresh control | Target lag | Automatic on change |
| Optimizer rewrite | No | Yes (transparent substitution) |
| Chaining | Yes | No |
| Use case | Data pipelines | Query acceleration |
Advanced Configuration¶
Warehouse Selection¶
-- Separate warehouse for dynamic table refresh
CREATE WAREHOUSE dt_refresh_wh
WAREHOUSE_SIZE = 'MEDIUM'
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE;
CREATE DYNAMIC TABLE my_dt
TARGET_LAG = '1 hour'
WAREHOUSE = dt_refresh_wh
AS
SELECT ...;
- Use dedicated warehouses for dynamic table refresh
- Size warehouse based on transformation complexity
- Short auto-suspend since refresh is periodic
- Separate from user query warehouses for workload isolation
Altering Dynamic Tables¶
-- Change target lag
ALTER DYNAMIC TABLE daily_summary SET TARGET_LAG = '2 hours';
-- Change warehouse
ALTER DYNAMIC TABLE daily_summary SET WAREHOUSE = larger_wh;
-- Suspend refresh
ALTER DYNAMIC TABLE daily_summary SUSPEND;
-- Resume refresh
ALTER DYNAMIC TABLE daily_summary RESUME;
-- Force full refresh
ALTER DYNAMIC TABLE daily_summary REFRESH;
Cost Management¶
-- Monitor dynamic table credit usage
SELECT start_time, end_time, table_name,
credits_used, num_rows_inserted, num_rows_deleted
FROM TABLE(INFORMATION_SCHEMA.DYNAMIC_TABLE_REFRESH_HISTORY())
WHERE start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
ORDER BY credits_used DESC;
- Credits consumed during refresh (warehouse compute)
- Shorter target lag = more refreshes = higher cost
- Use DOWNSTREAM lag for intermediate tables to minimize refreshes
- Monitor refresh frequency and duration for cost optimization
- Consider longer target lag for non-critical downstream consumers
Best Practices¶
Pipeline Design¶
- Layer your pipeline - separate cleansing, enrichment, and aggregation
- Use DOWNSTREAM for intermediates - let leaf nodes drive refresh cadence
- Right-size target lag - match business SLA, not aspirational freshness
- Monitor refresh duration - ensure refresh completes within target lag
- Test incrementally - verify incremental refresh works for your SQL patterns
Performance¶
- Warehouse sizing - match to transformation complexity
- Minimize full refreshes - use SQL patterns that support incremental
- Avoid non-deterministic functions - they force full refresh
- Limit pipeline depth - deep chains add cumulative lag
- Cluster output tables - if downstream queries benefit from clustering