Use streams on dynamic tables¶
A stream on a dynamic table captures row-level inserts, updates, and deletes. Each read returns the net changes since the previous read. This lets you bridge a dynamic table pipeline into targets that dynamic tables can’t reach on their own, such as stored procedures, external API calls, or conditional logic in a task.
This example assumes you have an existing dynamic table named dt_orders that uses an incremental
refresh. See Create a dynamic table for setup guidance.
Important
Use the ON DYNAMIC TABLE syntax when creating the stream. ON TABLE doesn’t work with dynamic tables and returns an error. See Common error for an example.
Supported stream types¶
Streams on dynamic tables support only the standard (delta) type. The standard stream captures inserts, updates, and deletes as net changes since the stream was last read.
APPEND_ONLY is not supported for streams on dynamic tables. If you need
insert-only semantics, consume the standard stream and filter on METADATA$ACTION = 'INSERT' in
your downstream task.
Note
The dynamic table must use an incremental refresh. Dynamic tables that use a full refresh don’t
support streams because each refresh replaces the entire table output, so there is no incremental
change history to track. If REFRESH_MODE is set to AUTO, verify the resolved mode by running
SHOW DYNAMIC TABLES and checking the refresh_mode column before creating a stream.
When to use this pattern¶
The primary use case is a hybrid pipeline where a dynamic table handles the declarative definition and a triggered task handles imperative work the dynamic table can’t express:
Dynamic table -> stream -> triggered task -> target
Common scenarios:
- Writing to a non-dynamic-table target (regular table, external stage).
- Calling a stored procedure or external function with side effects.
- Running conditional logic (IF/ELSE, loops) on the change set.
Use SYSTEM$STREAM_HAS_DATA in the task’s WHEN clause so the task fires only when the stream has unconsumed changes. The task runs after the dynamic table refreshes and produces new output. A refresh that changes no rows doesn’t trigger the task.
If multiple tasks consume the same dynamic table, create a separate stream for each. See Introduction to streams for details.
Stream behavior after reinitialization¶
When a dynamic table reinitializes (for example, after a CREATE OR REPLACE on a base table, a masking policy change, or a change-tracking toggle), the stream survives but its next read can return a much larger set of change rows than a normal refresh produces. The stream remains attached and exposes all row-level differences between its last-read offset and the reinitialized output.
Warning
After reinitialization, rows that were previously updates will appear as DELETE + INSERT pairs with METADATA$ISUPDATE set to FALSE. Downstream logic that relies on METADATA$ISUPDATE to distinguish real deletes from updates can lose or duplicate data without an error. Use idempotent MERGE statements rather than conditional INSERT/DELETE branching to handle this safely.
The following MERGE handles both normal changes and the DELETE + INSERT pairs that appear after reinitialization. The three clauses cover deletes, updates, and inserts by matching on the primary key:
After reinitialization, every surviving row appears as a DELETE + INSERT pair with
METADATA$ISUPDATE = FALSE. The MERGE processes each pair correctly: Clause 1 removes the old row,
Clause 3 re-inserts it. The net effect is the same data, with no duplicates or data loss. For normal
changes, Clause 2 handles updates in place.
Design your downstream task to handle these large change sets gracefully. Use idempotent inserts or batch processing so that a post-reinitialization read doesn’t cause duplicate or missing data.
Limitation: streams capture net changes, not full history¶
Don’t use a stream on a dynamic table as an audit log. If a row is inserted and then updated within a single refresh cycle, the stream shows only the final state. If the stream is not read between two refreshes, the changes from both refreshes collapse into a single net result.
If you need every state transition for compliance or auditing, write directly to an append-only table from your ingestion layer instead. See Introduction to streams for more on how streams track changes.
Verify the stream¶
After creating the stream, confirm it is attached and not stale:
Output columns are truncated for readability. Check the stale column to confirm the stream is
active. If stale shows true, the stream has fallen behind the dynamic table’s change history
and must be recreated. To prevent staleness, make sure the stream is read regularly within the data
retention period. See Introduction to streams for details on stream
staleness.
Common error¶
Using ON TABLE instead of ON DYNAMIC TABLE returns an error:
To create a stream on a dynamic table, use the ON DYNAMIC TABLE syntax:
Required privileges¶
To create a stream on a dynamic table, your role needs:
- CREATE STREAM on the schema.
- SELECT on the dynamic table.
For the full list of access control requirements, see CREATE STREAM.
What’s next¶
- To learn how dynamic table refreshes work, see Dynamic table refresh modes.
- To monitor refresh health for the upstream dynamic table, see Monitor dynamic tables.
- To troubleshoot refresh failures that affect streams, see Troubleshoot dynamic table refresh issues.
- For the full stream SQL reference, see CREATE STREAM.