Supported queries for dynamic tables¶
This page lists which SQL constructs, functions, and data types are supported in each refresh mode. Use it before creating dynamic tables to predict whether a definition qualifies for incremental refresh.
Recently added query support
- MIN_BY / MAX_BY aggregate and window functions (Mar 2026)
- Expanded outer join patterns including self-joins (Apr 2026)
- Primary key-based incremental refresh downstream of full-refresh dynamic tables (Apr 2026)
- UNION set operator for incremental refresh (Aug 2025)
- Cortex AI functions in SELECT clause for incremental refresh (Sep 2025)
- CURRENT_TIMESTAMP/DATE/TIME filtering in WHERE/HAVING/QUALIFY (May 2025)
ADAPTIVE refresh and query support
ADAPTIVE has the same query construct requirements as incremental refresh. For details on ADAPTIVE behavior, see ADAPTIVE refresh.
If your definition contains a construct that is not supported for incremental refresh, Snowflake selects a full refresh instead, which reprocesses all data on every refresh. For cost implications, see Understanding costs for dynamic tables.
For guidance on how query patterns affect incremental refresh performance, see Optimize queries for incremental refresh.
Supported data types¶
Dynamic tables support all Snowflake SQL data types for both incremental and full refresh, except:
- Structured data types (structured OBJECT, structured ARRAY, and MAP). This limitation applies to both incremental and full refresh. Semi-structured types (VARIANT, OBJECT, ARRAY without a defined schema) are fully supported.
- Geospatial data types (full refresh only).
Incremental and full refresh support matrix¶
The following table shows which SQL constructs are supported in each refresh mode. Where a construct is supported for incremental refresh with restrictions, the table describes the specific conditions.
| Construct | Incremental refresh | Full refresh |
|---|---|---|
| WITH | Supported when the CTE subquery uses only incrementally supported features. WITH RECURSIVE is not supported. | Supported |
| CONNECT BY | Not supported | Supported |
| SELECT | Supported. Expressions must use deterministic built-in functions and immutable user-defined functions. | Supported |
| DISTINCT | Supported | Supported |
| FROM | Base tables, views, Snowflake-managed Apache Iceberg™ tables, externally managed Apache Iceberg™ tables, Delta Direct tables, and other dynamic tables. Subqueries outside of FROM clauses (for example, WHERE EXISTS) are not supported. | Supported |
| WHERE / HAVING / QUALIFY | Filters with the same expressions that are valid in SELECT are supported. Filters with the CURRENT_TIMESTAMP, CURRENT_TIME, and CURRENT_DATE functions and their aliases are supported. | Supported. Filters with the CURRENT_TIMESTAMP, CURRENT_TIME, and CURRENT_DATE functions and their aliases are supported. |
| GROUP BY | Supported. GROUP BY ROLLUP, GROUP BY CUBE, and GROUP BY GROUPING SETS are not supported for incremental refresh. | Supported |
| Scalar aggregates | Supported | Supported |
| INNER JOIN | Supported. You can specify any number of tables, and Snowflake tracks changes to all tables in the join. | Supported |
| CROSS JOIN | Supported. You can specify any number of tables, and Snowflake tracks changes to all tables in the join. | Supported |
| [LEFT | RIGHT | FULL] OUTER JOIN | Supported with equality predicates only. Outer joins with non-equality predicates (for example, Self-joins (where both sides of the outer join reference the same table) are supported. You can specify any number of tables, and Snowflake tracks changes to all tables in the join. See LEFT JOIN example. | Supported |
| LATERAL JOIN | Not supported. However, you can use LATERAL with FLATTEN(). When using AUTO, Snowflake usually resolves to incremental refresh for definitions with lateral flatten joins, unless the definition contains other unsupported constructs. Selecting the flatten SEQ column from a lateral flatten join is not supported for incremental refresh. | Supported |
| UNION ALL | Supported. See UNION ALL example. | Supported |
| UNION (without ALL) | Supported. Behaves like UNION ALL combined with SELECT DISTINCT. | Supported |
| MINUS, EXCEPT, INTERSECT | Not supported | Supported |
| ORDER BY | Accepted but has no effect. Dynamic tables have no guaranteed row order. | Accepted but has no effect. Dynamic tables have no guaranteed row order. |
| LIMIT / FETCH / TOP <n> | Not supported | Supported |
| Window functions | Supported, except for the following:
| Supported |
| User-defined functions (UDFs and UDTFs) | Supported with restrictions. See User-defined functions below. | Supported |
| ML or LLM functions | Supported in the SELECT clause. | Supported |
| All subquery operators | Not supported | Supported |
| External functions | Not supported | Not supported |
| PIVOT | Supported when using a fixed value list in the IN clause. Not supported when using a subquery in the IN clause. | Supported |
| UNPIVOT | Not supported | Supported |
| SAMPLE / TABLESAMPLE | Not supported | Not supported |
| Sequences | Not supported | Supported |
Examples for common constructs¶
The following examples demonstrate common SQL patterns that qualify for incremental refresh.
Example: LEFT JOIN with incremental refresh¶
The following example creates a dynamic table that LEFT JOINs order data with customer data. Because the LEFT JOIN uses an equality predicate, this definition qualifies for incremental refresh.
Orders without a matching customer appear in the results with NULL values for customer_name and region.
Snowflake tracks changes to both raw_orders and dim_customers.
Example: UNION ALL with incremental refresh¶
The following example combines rows from two base tables using UNION ALL. Both branches must produce compatible column lists.
Snowflake tracks changes to both raw_orders_us and raw_orders_eu independently. New rows inserted into
either table are processed incrementally.
Example: LATERAL FLATTEN with incremental refresh¶
The following example flattens a semi-structured VARIANT column using LATERAL FLATTEN, which is supported for incremental refresh.
First, create a base table with nested JSON data:
Supported non-deterministic functions¶
Many non-deterministic functions are supported for incremental refresh. Timestamp functions (CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME) work in WHERE, HAVING, and QUALIFY clauses. Session-context functions (CURRENT_USER, CURRENT_ROLE, CURRENT_WAREHOUSE) and sequence functions are restricted. The following table shows the full matrix.
Tip
METADATA$ROW_LAST_COMMIT_TIME provides the commit time of each row and is compatible with incremental refresh. Use it instead of CURRENT_TIMESTAMP() in the SELECT list when you need a refresh timestamp without forcing full refresh mode. See Use row timestamps to measure latency in your pipelines.
| Non-deterministic function | Incremental refresh | Full refresh |
|---|---|---|
| ANY_VALUE | Not supported | Not supported |
| APPROX_PERCENTILE | Not supported | Supported |
| APPROX_TOP_K | Not supported | Supported |
| AI_CLASSIFY | Supported in the SELECT clause | Supported |
| AI_COMPLETE | Supported in the SELECT clause | Supported |
| CURRENT_ACCOUNT | Not supported | Supported |
| CURRENT_DATE (and aliases) | Supported only as a part of a WHERE/HAVING/QUALIFY clause. | Supported only as a part of a WHERE/HAVING/QUALIFY clause. |
| CURRENT_REGION | Not supported | Supported |
| CURRENT_ROLE | Not supported | Supported |
| CURRENT_TIME (and aliases) | Supported only as a part of a WHERE/HAVING/QUALIFY clause. | Supported only as a part of a WHERE/HAVING/QUALIFY clause. |
| CURRENT_TIMESTAMP (and aliases) | Supported only as a part of a WHERE/HAVING/QUALIFY clause. | Supported only as a part of a WHERE/HAVING/QUALIFY clause. |
| Functions that rely on CURRENT_USER. | Not supported. Dynamic table refreshes act as their owner role with a special SYSTEM user. | Not supported. Dynamic table refreshes act as their owner role with a special SYSTEM user. |
| CURRENT_WAREHOUSE | Not supported | Supported |
| DENSE_RANK | Supported | Supported |
| AI_EMBED | Supported in the SELECT clause | Supported |
| AI_EXTRACT | Supported in the SELECT clause | Supported |
| FINETUNE (SNOWFLAKE.CORTEX) | Supported in the SELECT clause | Supported |
| FIRST_VALUE | Supported | Supported |
| LAST_VALUE | Supported | Supported |
| MAX_BY | Supported | Supported |
| MIN_BY | Supported | Supported |
| NTH_VALUE | Supported | Supported |
| RANK | Supported | Supported |
| ROW_NUMBER | Supported | Supported |
| SENTIMENT (SNOWFLAKE.CORTEX) | Supported in the SELECT clause | Supported |
Sequence functions (such as SEQ1, SEQ2) | Not supported | Supported |
| AI_TRANSLATE | Supported in the SELECT clause | Supported |
| VOLATILE user-defined functions | Not supported | Supported |
User-defined functions¶
UDFs are supported in dynamic table definitions. Whether a UDF supports incremental refresh depends on the function type and volatility:
Scalar UDFs:
- IMMUTABLE: compatible with incremental refresh
- VOLATILE (the default): force full refresh
- SQL UDFs containing subqueries: not supported. Dynamic table creation fails regardless of the refresh mode.
Table functions (UDTFs):
- Supported in lateral joins only
- UDTFs always force full refresh regardless of volatility
UDFs default to VOLATILE. To enable incremental refresh, create the function with the IMMUTABLE property. Only use IMMUTABLE if the function always returns the same output for the same input. Snowflake doesn’t validate this, and incorrect use can produce wrong results.
If you replace an IMMUTABLE UDF that an existing dynamic table uses, subsequent incremental refreshes fail. Recreate the dynamic table after replacing the UDF.
For details, see User-defined functions overview.
Supported Snowflake Cortex AI functions¶
You can use Snowflake Cortex AI Functions (including LLM functions) in the SELECT clause for dynamic tables in incremental refresh mode. The same availability restrictions as described in Cortex AI functions apply.
Cortex AI functions let you add AI-powered insights directly to your dynamic tables, automatically analyzing data as it refreshes. For example, you can classify customer reviews, support tickets, or survey responses.
Place Cortex AI functions in the SELECT clause only
Cortex AI functions are supported for incremental refresh only in the SELECT clause. Placing them in WHERE, GROUP BY, HAVING, or QUALIFY clauses is not supported.
In the following example, dt_review_sentiment uses AI_FILTER to evaluate each review with an LLM. The output column
enjoyed indicates whether the reviewer enjoyed the restaurant, based on the prompt.
What causes fallback to full refresh¶
The following conditions cause Snowflake to use full refresh instead of incremental refresh. When REFRESH_MODE = INCREMENTAL, creation fails if any condition applies. When REFRESH_MODE = AUTO, creation succeeds but resolves to FULL at creation time. For details on how AUTO chooses between incremental and full refresh, see Dynamic table refresh modes.
| Condition | Category |
|---|---|
| Definition contains EXCEPT, INTERSECT, or MINUS | Definition shape |
| Definition contains LIMIT / TOP | Definition shape |
| GROUP BY ROLLUP, GROUP BY CUBE, or GROUP BY GROUPING SETS | Definition shape |
Outer joins with non-equality predicates (such as ON a.id > b.id) | Definition shape |
| WITH RECURSIVE | Definition shape |
| Subqueries outside FROM clauses (such as WHERE EXISTS, WHERE IN (SELECT …)) | Definition shape |
| Non-deterministic functions in SELECT (such as RANDOM(), UUID_STRING(), CURRENT_TIMESTAMP()) | Function type |
| VOLATILE UDFs written in Python, Java, Scala, or JavaScript | Function type |
| SQL UDFs that contain subqueries | Function type |
| External functions | Function type |
| An upstream dynamic table uses FULL refresh (unless it has a system-derived unique key) | Pipeline shape |
| Change tracking is not enabled on a base table | Configuration |
To identify why a dynamic table resolved to full refresh, query the refresh_mode_reason column:
Common refresh_mode_reason values include:
| Value | Meaning |
|---|---|
| QUERY_NOT_SUPPORTED_FOR_INCREMENTAL | The definition contains constructs that are not supported for incremental refresh. |
| USER_SPECIFIED_FULL_REFRESH | The dynamic table was created with REFRESH_MODE = FULL. |
| UPSTREAM_USES_FULL_REFRESH | An upstream dynamic table uses full refresh and does not have a system-derived unique key. |
| NULL | Incremental refresh is supported. No fallback reason. |
Additional values may appear in future releases.
Tip
If your query uses a construct that forces full refresh and you need incremental processing, consider custom incrementalization. Custom incremental dynamic tables bypass standard query analysis and let you define refresh logic directly as MERGE or INSERT DML.
What happens when you use an unsupported construct with INCREMENTAL¶
If you explicitly set REFRESH_MODE = INCREMENTAL and the definition contains a construct that is not supported
for incremental refresh, the CREATE DYNAMIC TABLE statement fails with an error:
When you use REFRESH_MODE = AUTO instead, the same definition succeeds but resolves to FULL.
Incremental refresh constraints¶
The following constraints apply specifically to dynamic tables that use incremental refresh.
Incremental refresh on upstream full-refresh dynamic tables¶
Dynamic tables in incremental refresh mode can’t consume an upstream dynamic table that uses full refresh unless the upstream full-refresh dynamic table has a system-derived unique key or a frozen region (a snapshot of rows that won’t change). When either condition is met, Snowflake computes row-level changes across full refreshes, enabling downstream incremental processing.
To use this capability, set REFRESH_MODE = INCREMENTAL explicitly on the downstream dynamic table. AUTO doesn’t
resolve to incremental in this scenario.
For more information, see Optimize input data for dynamic tables.
Masking and row access policies¶
Masking or row access policies on a dynamic table don’t affect its refresh mode. However, policies applied on base tables might affect the refresh mode:
- Incremental refresh is supported if the policies on base tables use the CURRENT_ROLE or IS_ROLE_IN_SESSION function.
- Incremental refresh isn’t supported if the policies on base tables use any other functions, INFORMATION_SCHEMA views, or query a table (for example, a mapping table lookup).
- Changes to the policies on base objects of dynamic tables with incremental refresh trigger reinitialization.
Replication¶
Replicated dynamic tables with incremental refresh reinitialize after failover before they can resume incremental refresh.
For more information, see Replication and dynamic tables.
Cloning¶
Cloned incremental dynamic tables might need to reinitialize during their initial refresh after being created.
If a dynamic table is cloned from another dynamic table with dropped base tables, the clone is suspended and can’t be resumed or refreshed.
What’s next¶
- To optimize query patterns for incremental refresh performance, see Optimize queries for incremental refresh.
- To define refresh logic directly with MERGE or INSERT statements, see Custom incrementalization.
- To understand the cost differences between incremental and full refresh, see Understanding costs for dynamic tables.
- To troubleshoot refresh issues, see Troubleshoot dynamic table refresh issues.
- For the full CREATE DYNAMIC TABLE syntax, see CREATE DYNAMIC TABLE.