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.
ADAPTIVE has the same query construct requirements as incremental refresh: if a construct is listed as unsupported for incremental refresh, it is also unsupported for 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.
AUTO resolves once at creation time
When you set REFRESH_MODE = AUTO, Snowflake picks INCREMENTAL or FULL once at
creation time. If AUTO picks FULL, every subsequent refresh is full until you recreate the table.
To verify the resolved mode and understand how AUTO chooses, see Dynamic table refresh modes.
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 |
| SELECT | Expressions using deterministic built-in functions and immutable user-defined functions. | Supported |
| DISTINCT | Supported | Supported |
| FROM | Base tables, views, Snowflake-managed Apache Iceberg™ 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 |
| Set operators (UNION, MINUS, EXCEPT, INTERSECT) | Not supported, except for UNION (without ALL). In incremental refresh, the UNION operator behaves like UNION ALL combined with SELECT DISTINCT. MINUS, EXCEPT, and INTERSECT are not supported for incremental refresh. | Supported |
| ORDER BY | Not supported | Supported |
| 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 and UNPIVOT | Not supported | Not 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 |
| 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: dynamic table creation fails
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.
Non-deterministic functions block incremental refresh for the entire definition
If your Cortex function call includes a non-deterministic expression in its arguments (for example,
CURRENT_DATE() inside a prompt string), the entire dynamic table definition loses incremental refresh eligibility.
This applies even if the non-deterministic function is buried inside string concatenation.
To avoid this, use a filter in the WHERE clause instead, or pass date values from a column rather than calling a function.
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¶
When you set REFRESH_MODE = INCREMENTAL or REFRESH_MODE = AUTO, Snowflake evaluates whether the definition
supports incremental refresh at creation time. With INCREMENTAL, creation fails if unsupported. With AUTO, creation succeeds but resolves to FULL. The following conditions cause Snowflake to select full refresh instead:
| Condition | Category |
|---|---|
| Definition contains EXCEPT, INTERSECT, or MINUS | Definition shape |
| Definition contains ORDER BY or LIMIT / TOP | Definition shape |
| GROUP BY ROLLUP, GROUP BY CUBE, or GROUP BY GROUPING SETS | Definition shape |
Outer joins with non-equality predicates (for example, ON a.id > b.id) | Definition shape |
| WITH RECURSIVE | Definition shape |
| Subqueries outside FROM clauses (for example, 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. 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 take full control of refresh logic with custom 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.