8.5 Schema Detection, Schema Evolution & Source-Change Architecture

Key Takeaways

  • INFER_SCHEMA reads staged Parquet, Avro, ORC, JSON, or CSV files and returns column names, types, and nullability, and CREATE TABLE ... USING TEMPLATE turns that output into a table definition.
  • Schema evolution adds new columns and drops NOT NULL from columns missing in new files when the table has ENABLE_SCHEMA_EVOLUTION = TRUE, the COPY uses MATCH_BY_COLUMN_NAME, and the loading role has EVOLVE SCHEMA or OWNERSHIP on the table.
  • Schema evolution applies to COPY INTO <table> and Snowpipe (not INSERT); by default one COPY can add at most 100 columns or evolve one schema, and CSV evolution also needs PARSE_HEADER and ERROR_ON_COLUMN_COUNT_MISMATCH = FALSE.
  • Snowpipe Streaming's high-performance architecture and the Kafka connector can also evolve table schemas, while VARIANT landing columns absorb source changes without any DDL.
  • Architects choose between strict schemas (fail fast on drift), automatic evolution (accept additive changes), and VARIANT landing plus typed downstream models (absorb any change, type later).
Last updated: September 2026

Why Source Changes Are an Architecture Problem

The ARA-C01 blueprint (objective 3.1) asks architects to design loading solutions that handle architecture changes: schema detection and table schema evolution and data source changes. In practice this means deciding what should happen when tomorrow's files contain a column that today's table does not have.

StrategyBehavior on a new source columnBest for
Strict schemaLoad fails or the column is ignored; engineers change DDL deliberatelyRegulated or contract-driven feeds
Automatic schema evolutionSnowflake adds the column during the loadAdditive, trusted feeds from many producers
VARIANT landingWhole record lands in a VARIANT column; nothing breaksHighly variable JSON/event data; typing happens downstream

Schema Detection with INFER_SCHEMA

INFER_SCHEMA is a table function that reads staged files and returns the detected column definitions: COLUMN_NAME, TYPE, NULLABLE, EXPRESSION, FILENAMES, and ORDER_ID. It supports Apache Parquet, Apache Avro, ORC, JSON, and CSV files.

CREATE FILE FORMAT raw.formats.parquet_ff TYPE = PARQUET;

-- Inspect what Snowflake detects in the staged files
SELECT *
FROM TABLE(INFER_SCHEMA(
  LOCATION => '@raw.stages.orders_stage/2026/09/',
  FILE_FORMAT => 'raw.formats.parquet_ff',
  MAX_FILE_COUNT => 10));

-- Create a table whose columns come straight from the detected schema
CREATE TABLE raw.orders
  USING TEMPLATE (
    SELECT ARRAY_AGG(OBJECT_CONSTRUCT(*))
    FROM TABLE(INFER_SCHEMA(
      LOCATION => '@raw.stages.orders_stage/2026/09/',
      FILE_FORMAT => 'raw.formats.parquet_ff')));

Useful details:

  • USING TEMPLATE also works with CREATE EXTERNAL TABLE and CREATE ICEBERG TABLE.
  • For CSV, the file format needs PARSE_HEADER = TRUE so column names come from the header row.
  • GENERATE_COLUMN_DESCRIPTION turns INFER_SCHEMA output into a column list you can paste into DDL.
  • MAX_FILE_COUNT and MAX_RECORDS_PER_FILE limit how much data is scanned, which matters for very large prefixes.

Table Schema Evolution

Schema evolution lets a load change the target table automatically:

  • Adds new columns found in the incoming files.
  • Drops the NOT NULL constraint from columns that are missing in new data files.

All of these must be true for a file load to evolve the table:

  1. The table has ENABLE_SCHEMA_EVOLUTION = TRUE (set at CREATE TABLE or with ALTER TABLE).
  2. The COPY INTO <table> uses MATCH_BY_COLUMN_NAME (CASE_SENSITIVE or CASE_INSENSITIVE).
  3. The role running the load has EVOLVE SCHEMA or OWNERSHIP on the table.
  4. For CSV, the file format uses PARSE_HEADER = TRUE and ERROR_ON_COLUMN_COUNT_MISMATCH = FALSE.
ALTER TABLE raw.orders SET ENABLE_SCHEMA_EVOLUTION = TRUE;
GRANT EVOLVE SCHEMA ON TABLE raw.orders TO ROLE loader_role;

COPY INTO raw.orders
  FROM @raw.stages.orders_stage
  FILE_FORMAT = (FORMAT_NAME = 'raw.formats.parquet_ff')
  MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE;

Limits and scope:

  • Evolution applies to COPY INTO <table> and Snowpipe loads; INSERT statements never evolve a schema.
  • By default a single COPY can add at most 100 columns or evolve one schema; Snowflake Support can raise this.
  • Snowpipe Streaming (high-performance architecture) supports schema evolution for standard tables and Snowflake-managed Iceberg tables (adding top-level columns), and the Kafka connector with Snowpipe Streaming Classic supports schema detection and evolution.
  • Evolution is additive: it does not rename columns, change types, or drop columns. Renames and type changes still require planned DDL.

Designing for Data Source Changes

A robust layered design absorbs change at the edge and exposes stable contracts to consumers:

  1. Landing layer: either evolve automatically (typed columns) or land raw records in a VARIANT column plus metadata (METADATA$FILENAME, load timestamp). VARIANT landing never fails on new attributes.
  2. Transformation layer: dynamic tables, streams and tasks, or dbt models project the fields you actually support, with explicit casts. New source fields do not reach consumers until someone models them.
  3. Consumption layer: secure views or curated tables keep column names and types stable, so BI tools and shares do not break.
  4. Monitoring: compare INFER_SCHEMA output with the table's columns in a scheduled task or alert, and review LOAD_HISTORY/COPY_HISTORY errors after source releases.

Exam Trap: Enabling ENABLE_SCHEMA_EVOLUTION alone does nothing if the COPY statement maps columns by position. Without MATCH_BY_COLUMN_NAME, the load cannot evolve the table.

Worked Example: Evolving a CSV Partner Feed

A partner sends daily CSV files with a header row. In March they add a loyalty_tier column, and in June a middle_name column is dropped from their export.

CREATE FILE FORMAT raw.formats.partner_csv
  TYPE = CSV
  PARSE_HEADER = TRUE                     -- read column names from the header row
  ERROR_ON_COLUMN_COUNT_MISMATCH = FALSE; -- required for CSV schema evolution

CREATE TABLE raw.partner_customers
  USING TEMPLATE (
    SELECT ARRAY_AGG(OBJECT_CONSTRUCT(*))
    FROM TABLE(INFER_SCHEMA(LOCATION => '@raw.stages.partner/2026/01/',
                            FILE_FORMAT => 'raw.formats.partner_csv')))
  ENABLE_SCHEMA_EVOLUTION = TRUE;

COPY INTO raw.partner_customers
  FROM @raw.stages.partner
  FILE_FORMAT = (FORMAT_NAME = 'raw.formats.partner_csv')
  MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE;

What happens:

  • March: the load adds LOYALTY_TIER to the table automatically; earlier rows hold NULL in it.
  • June: files no longer contain MIDDLE_NAME. If that column was NOT NULL, schema evolution drops the NOT NULL constraint so loads keep working; the column stays in the table with NULLs for new rows.
  • The load history (COPY_HISTORY, LOAD_HISTORY) and the table's DDL show the change, so downstream owners can decide whether to model the new field.

Changes Schema Evolution Does Not Handle

Source changeWhat happensArchitect's response
New columnAdded automatically (if evolution is enabled)Decide whether downstream layers expose it
Column missing in new filesNOT NULL dropped; column keptConfirm with the source whether it is deprecated
Column renamedTreated as a new column; the old one stops receiving dataMap old and new names in the transformation layer
Type changed (for example number to text)Not evolved; values that no longer convert cause load errorsLand raw values (VARIANT or VARCHAR) or coordinate a planned DDL change
New file format or delimiterNot detected by evolutionVersion the file format object and route files by prefix

For external tables and Iceberg tables, USING TEMPLATE can create the initial definition from INFER_SCHEMA, but later changes are handled by refreshing or altering those tables (and, for Iceberg, by the table format's own schema evolution) rather than by ENABLE_SCHEMA_EVOLUTION.

Loading diagram...
Handling Source Schema Changes Across Layers
Test Your Knowledge

A table has ENABLE_SCHEMA_EVOLUTION = TRUE. The nightly load runs COPY INTO raw.events FROM @events_stage FILE_FORMAT = (TYPE = PARQUET); and a new column that appeared in the files is not added to the table. What is missing?

A
B
C
D
Test Your Knowledge

An architect wants to create a table whose columns exactly match a set of staged Avro files, without hand-writing the DDL. Which approach does Snowflake provide?

A
B
C
D
Test Your Knowledge

Source teams frequently add optional attributes to JSON events, and downstream BI dashboards must never break when that happens. Which design best meets both needs?

A
B
C
D