Snowflake

Dynamic Tables vs Streams/Tasks: When to Use Which

2026-03-26
更新: 2026-03-27
NicheeLab Editorial Team

When building data pipelines in Snowflake, choosing between Dynamic Tables and Streams + Tasks is a critical architectural decision. The two follow completely different paradigms and suit different use cases. This article compares them across 7 axes and gives you a framework for making the call.

Axis 1: Declarative vs Imperative

Dynamic Tables are declarative. You simply declare "keep the result of this SELECT statement up to date," and Snowflake handles change detection, incremental processing, and refresh scheduling for you.

Streams + Tasks are imperative. You explicitly code the steps: use a Stream to capture CDC (change data capture) and a Task to run MERGE/INSERT statements. Transformation logic, execution order, and error handling are all under your control.

-- 宣言型: Dynamic Table
CREATE DYNAMIC TABLE gold_revenue
  TARGET_LAG = '10 minutes'
  WAREHOUSE = etl_wh
AS
  SELECT region, SUM(amount) AS total_revenue
  FROM silver_orders
  GROUP BY region;

-- 手続型: Stream + Task
CREATE STREAM orders_stream ON TABLE silver_orders;

CREATE TASK merge_revenue
  WAREHOUSE = etl_wh
  SCHEDULE = 'USING CRON */10 * * * * UTC'
  WHEN SYSTEM$STREAM_HAS_DATA('orders_stream')
AS
  MERGE INTO gold_revenue t
  USING (
    SELECT region, SUM(amount) AS total_revenue
    FROM orders_stream
    GROUP BY region
  ) s ON t.region = s.region
  WHEN MATCHED THEN UPDATE SET t.total_revenue = t.total_revenue + s.total_revenue
  WHEN NOT MATCHED THEN INSERT (region, total_revenue) VALUES (s.region, s.total_revenue);

Axis 2: SLA Control

AspectDynamic TablesStreams + Tasks
Latency controlTARGET_LAG (declare max acceptable latency)CRON schedule (explicit execution interval)
Specify "run at minute 0 of every hour"Not supported (lag-based control only)Yes (strict CRON expression)
Behavior when there are no changesRefresh is skipped automaticallySkip explicitly via the WHEN clause
End-to-end DAG SLA managementAutomatically derived from leaf-node TARGET_LAGEach Task schedule must be designed individually

Axis 3: Development and Operational Complexity

ItemDynamic TablesStreams + Tasks
Initial code volumeSmall (CREATE DYNAMIC TABLE + SELECT)Larger (Stream + Task + MERGE/INSERT statements)
Handling schema changesEdit the SELECT and REPLACERecreate Stream + edit Task + re-schedule
Recovery from failuresAutomatic retry; manual REFRESH availableCheck Task error logs → check Stream offset → re-run manually
TestabilityThe SELECT can be tested in isolationStream consumption is not idempotent, so retesting takes care

Axis 4: JOIN Handling

Dynamic Tables let you write any JOIN inside the SELECT, and Snowflake tracks every upstream table for you. With Streams + Tasks, a Stream is created on a single table (or view), so to JOIN changes from multiple tables you either create multiple Streams and combine them inside the Task, or design around the timing skew of change detection across sources.

-- Dynamic Tables: JOINを含むSELECTをそのまま宣言
CREATE DYNAMIC TABLE enriched_orders
  TARGET_LAG = '5 minutes'
  WAREHOUSE = etl_wh
AS
  SELECT
    o.order_id,
    o.order_date,
    p.product_name,
    p.category,
    o.quantity * p.unit_price AS line_total
  FROM raw_orders o
  JOIN raw_products p ON o.product_id = p.product_id;

-- Streams/Tasks: 複数Streamの管理が必要
CREATE STREAM orders_cdc ON TABLE raw_orders;
CREATE STREAM products_cdc ON TABLE raw_products;
-- → Task内で両Streamを参照し、MERGEロジックを実装
-- → 片方のStreamだけにデータがあるケースの処理が必要

Axis 5: Incremental Logic

Dynamic Tables' incremental refresh is detected and executed automatically by Snowflake. You write the SELECT without worrying about refresh mode. Keep in mind, though, that queries containing WINDOW functions or SELECT DISTINCT fall back to Full Refresh.

With Streams + Tasks, the Stream exposes INSERT/UPDATE/DELETE rows through columns like METADATA$ACTION, so you can write a MERGE statement that handles incremental processing with fine-grained precision. Tracking DELETE operations or comparing before/after values on UPDATEs are all possible.

Axis 6: Cost Structure

Cost itemDynamic TablesStreams + Tasks
Compute costCredits on the specified warehouseCredits on the specified warehouse
Storage costStorage for the materialized tableStorage for the target table
Cost when there are no changesRefresh skipped (effectively zero)Skipped via WHEN clause (effectively zero)
When a Full Refresh occursCosts can spike on large tablesStream-based: only deltas are processed
Monitoring viewDYNAMIC_TABLE_REFRESH_HISTORYTASK_HISTORY / STREAM_HAS_DATA

Axis 7: Monitoring and Observability

-- Dynamic Tablesのリフレッシュ監視
SELECT name, refresh_action, state, data_timestamp
FROM TABLE(INFORMATION_SCHEMA.DYNAMIC_TABLE_REFRESH_HISTORY())
WHERE state = 'FAILED'
ORDER BY refresh_start_time DESC
LIMIT 10;

-- Streams/Tasksの実行監視
SELECT name, state, error_message, scheduled_time
FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY(
  SCHEDULED_TIME_RANGE_START => DATEADD(HOUR, -24, CURRENT_TIMESTAMP())
))
WHERE state = 'FAILED'
ORDER BY scheduled_time DESC;

Design Decision Flowchart

パイプラインの要件は?
│
├─ SELECT文で表現可能な変換か?
│   ├─ YES → JOINや集計が中心か?
│   │         ├─ YES → Dynamic Tables推奨
│   │         └─ NO(条件分岐・外部連携あり)→ Streams/Tasks
│   └─ NO(ストアドプロシージャが必要)→ Streams/Tasks
│
├─ 厳密なCRONスケジュールが必要か?
│   ├─ YES(毎時0分に実行等)→ Streams/Tasks
│   └─ NO(最大遅延指定でOK)→ Dynamic Tables
│
├─ DELETE/UPDATEの変更前後の値を追跡する必要があるか?
│   ├─ YES → Streams/Tasks(METADATA$ACTION / $ISUPDATE)
│   └─ NO → Dynamic Tables
│
└─ 運用負荷を最小化したいか?
    ├─ YES → Dynamic Tables
    └─ 細かい制御が必要 → Streams/Tasks

7-Axis Summary Comparison

AxisDynamic TablesStreams + Tasks
1. ParadigmDeclarativeImperative
2. SLA controlTARGET_LAG (max latency)CRON (execution timing)
3. ComplexityLow (just a SELECT)High (Stream + Task + MERGE)
4. JOINWrite freely inside the SELECTRequires managing multiple Streams
5. Incremental logicAuto-detectedManual (fine-grained control)
6. CostWatch out for Full RefreshesAlways processes deltas only
7. MonitoringREFRESH_HISTORYTASK_HISTORY

Check Your Understanding

Data Engineering

問題 1

A team operates a pipeline that detects changes on an orders table and updates an aggregate table via MERGE. The requirements are: (1) run exactly at 06:00 UTC every day, and (2) use the pre-change values of DELETEd rows to perform rollback aggregation. Which implementation approach is most appropriate?

  1. Build declaratively with Dynamic Tables (TARGET_LAG = '24 hours')
  2. Use Dynamic Tables (TARGET_LAG = DOWNSTREAM) to follow downstream
  3. Build with Streams + Tasks (CRON schedule + METADATA$ACTION)
  4. Use a Materialized View with automatic refresh

正解: C

Two of the requirements — a strict CRON-based schedule ("exactly 06:00 UTC every day") and fine-grained incremental control ("track pre/post values of DELETE rows") — are both outside what Dynamic Tables can do. Streams/Tasks can specify the schedule via CRON and identify DELETE/UPDATE rows through the Stream's METADATA$ACTION and METADATA$ISUPDATE columns. Materialized Views are limited to aggregations on a single table and do not support MERGE operations.

Frequently Asked Questions

Can Dynamic Tables and Streams/Tasks coexist in the same pipeline?

Yes, they coexist well. For example, you can capture changes on a Dynamic Table with a Stream and then fan out to external tables or email notifications via a Task. Change Tracking is automatically enabled when a Dynamic Table is used as a Stream source, so no extra configuration is required. Because the dependency graph can get complex, plan DAG visualization and alerting up front.

What should I watch out for when migrating from Streams/Tasks to Dynamic Tables?

The biggest gotcha is that Stream offsets (consumption position) do not carry over to Dynamic Tables. A Dynamic Table performs a Full Refresh on initial creation, so plan your cutover carefully to avoid duplicate processing during the migration window. Also note that Dynamic Tables cannot use strict CRON schedules — they rely on TARGET_LAG to declare maximum latency — so any pipeline with strict execution-order dependencies needs a design review.

Which is cheaper to run?

It depends. For simple ETL pipelines, Dynamic Tables tend to be cheaper thanks to automatic incremental-refresh optimization. On the other hand, Streams/Tasks let you skip Task execution entirely when there is no new data (WHEN SYSTEM$STREAM_HAS_DATA), so for tables that change very infrequently Streams/Tasks can consume fewer credits. Either way, measure actual credit consumption against the refresh-history views in ACCOUNT_USAGE before deciding.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Snowflake

Snowflake Certifications: All 11 Exams Explained (2026)

Every SnowPro certification — Associate, Core, Specialty, Ad...

Snowflake

Snowflake Exam Difficulty Ranking: All 11 Certs Compared (2026)

All 11 SnowPro exams ranked by difficulty with study-time es...

Snowflake

Snowflake Study Guide: Fastest Pass Route by Exam (2026)

How to pass SnowPro certifications efficiently — official ma...

Snowflake

SnowPro Core (COF-C03): Complete Exam Guide (2026)

Pass the SnowPro Core exam — six domains, scope, sample ques...

Snowflake

SnowPro Associate Platform (SOL-C01): Complete Guide (2026)

The entry-level SnowPro Associate exam — scope, weighting, s...

Browse all Snowflake articles (103)
© 2026 NicheeLab All rights reserved.