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.

ConstructIncremental refreshFull 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
DISTINCTSupportedSupported
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 aggregatesSupportedSupported
INNER JOINSupported. You can specify any number of tables, and Snowflake tracks changes to all tables in the join.Supported
CROSS JOINSupported. 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, ON a.id > b.id) are not supported for incremental refresh.

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 BYNot supportedSupported
LIMIT / FETCH / TOP <n>Not supportedSupported
Window functions

Supported, except for the following:

  • PERCENT_RANK, DENSE_RANK, or RANK with sliding window frames (for example, ROWS BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING).
  • ANY_VALUE is not supported because it is a non-deterministic function.
Supported
User-defined functions (UDFs and UDTFs)

Supported with restrictions. See User-defined functions below.

Supported
ML or LLM functionsSupported in the SELECT clause.Supported
All subquery operatorsNot supportedSupported
External functionsNot supportedNot supported
PIVOT and UNPIVOTNot supportedNot supported
SAMPLE / TABLESAMPLENot supportedNot supported
SequencesNot supportedSupported

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.

CREATE OR REPLACE DYNAMIC TABLE dt_orders_with_customers
    TARGET_LAG = '10 minutes'
    WAREHOUSE = transform_wh
    REFRESH_MODE = INCREMENTAL
AS
    SELECT
        o.order_id,
        o.order_date,
        o.product_name,
        o.quantity * o.unit_price AS line_total,
        c.customer_name,
        c.region
    FROM raw_orders o
    LEFT JOIN dim_customers c
        ON o.customer_id = c.customer_id;

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.

-- Base tables (simplified setup)
CREATE OR REPLACE TABLE raw_orders_us (
    order_id INT, customer_id INT, order_date TIMESTAMP_NTZ,
    product_name VARCHAR, quantity INT, unit_price DECIMAL(10,2),
    region VARCHAR DEFAULT 'US'
);

CREATE OR REPLACE TABLE raw_orders_eu (
    order_id INT, customer_id INT, order_date TIMESTAMP_NTZ,
    product_name VARCHAR, quantity INT, unit_price DECIMAL(10,2),
    region VARCHAR DEFAULT 'EU'
);

CREATE OR REPLACE DYNAMIC TABLE dt_combined_orders
    TARGET_LAG = '10 minutes'
    WAREHOUSE = transform_wh
    REFRESH_MODE = INCREMENTAL
AS
    SELECT order_id, customer_id, order_date, product_name, quantity, unit_price, region
    FROM raw_orders_us
    UNION ALL
    SELECT order_id, customer_id, order_date, product_name, quantity, unit_price, region
    FROM raw_orders_eu;

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:

CREATE OR REPLACE TABLE persons
 AS
  SELECT column1 AS id, parse_json(column2) AS entity
  FROM values
   (12712555,
   '{ name:  { first: "John", last: "Smith"},
     contact: [
     { business:[
       { type: "phone", content:"555-1234" },
       { type: "email", content:"j.smith@example.com" } ] } ] }'),
   (98127771,
    '{ name:  { first: "Jane", last: "Doe"},
     contact: [
     { business:[
       { type: "phone", content:"555-1236" },
       { type: "email", content:"j.doe@example.com" } ] } ] }');
CREATE OR REPLACE DYNAMIC TABLE dt_flattened_contacts
 TARGET_LAG = DOWNSTREAM
 WAREHOUSE = transform_wh
 AS
  SELECT p.id, f.value, f.path
  FROM persons p,
  LATERAL FLATTEN(input => p.entity) f;

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 functionIncremental refreshFull refresh
ANY_VALUENot supportedNot supported
AI_CLASSIFYSupported in the SELECT clauseSupported
AI_COMPLETESupported in the SELECT clauseSupported
CURRENT_ACCOUNTNot supportedSupported
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_REGIONNot supportedSupported
CURRENT_ROLENot supportedSupported
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_WAREHOUSENot supportedSupported
DENSE_RANKSupportedSupported
AI_EMBEDSupported in the SELECT clauseSupported
AI_EXTRACTSupported in the SELECT clauseSupported
FINETUNE (SNOWFLAKE.CORTEX)Supported in the SELECT clauseSupported
FIRST_VALUESupportedSupported
LAST_VALUESupportedSupported
MAX_BYSupportedSupported
MIN_BYSupportedSupported
NTH_VALUESupportedSupported
RANKSupportedSupported
ROW_NUMBERSupportedSupported
SENTIMENT (SNOWFLAKE.CORTEX)Supported in the SELECT clauseSupported
Sequence functions (such as SEQ1, SEQ2)Not supportedSupported
AI_TRANSLATESupported in the SELECT clauseSupported
VOLATILE user-defined functionsNot supportedSupported

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.

CREATE OR REPLACE TABLE reviews AS
  SELECT 'Wow... Loved this place.' AS review
  UNION ALL
  SELECT 'The pizza is not good.' AS review;

CREATE OR REPLACE DYNAMIC TABLE dt_review_sentiment
  TARGET_LAG = DOWNSTREAM
  WAREHOUSE = transform_wh
  REFRESH_MODE = INCREMENTAL
  AS
    SELECT review, AI_FILTER(CONCAT('The reviewer enjoyed the restaurant', review), {'model': 'llama3.1-70b'}) AS enjoyed FROM reviews;

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:

ConditionCategory
Definition contains EXCEPT, INTERSECT, or MINUSDefinition shape
Definition contains ORDER BY or LIMIT / TOPDefinition shape
GROUP BY ROLLUP, GROUP BY CUBE, or GROUP BY GROUPING SETSDefinition shape
Outer joins with non-equality predicates (for example, ON a.id > b.id)Definition shape
WITH RECURSIVEDefinition 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 JavaScriptFunction type
SQL UDFs that contain subqueriesFunction type
External functionsFunction 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 tableConfiguration

To identify why a dynamic table resolved to full refresh, query the refresh_mode_reason column:

SHOW DYNAMIC TABLES LIKE 'dt_orders';
+-------------------+-------------------------------------------+
| refresh_mode      | refresh_mode_reason                       |
|-------------------+-------------------------------------------|
| FULL              | QUERY_NOT_SUPPORTED_FOR_INCREMENTAL       |
+-------------------+-------------------------------------------+

Common refresh_mode_reason values include:

ValueMeaning
QUERY_NOT_SUPPORTED_FOR_INCREMENTALThe definition contains constructs that are not supported for incremental refresh.
USER_SPECIFIED_FULL_REFRESHThe dynamic table was created with REFRESH_MODE = FULL.
UPSTREAM_USES_FULL_REFRESHAn upstream dynamic table uses full refresh and does not have a system-derived unique key.
NULLIncremental 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:

CREATE OR REPLACE DYNAMIC TABLE dt_unsupported_example
    TARGET_LAG = '10 minutes'
    WAREHOUSE = transform_wh
    REFRESH_MODE = INCREMENTAL
AS
    SELECT order_id, order_date
    FROM raw_orders
    EXCEPT
    SELECT order_id, order_date
    FROM excluded_orders;
002711 (0A000): SQL compilation error:
  Dynamic Table 'DT_UNSUPPORTED_EXAMPLE' is defined with REFRESH_MODE = INCREMENTAL,
  but the query is not supported for incremental refresh.

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