Home Database Data Loading: Stages, COPY INTO & File Formats
Intermediate 4 min · July 17, 2026

Data Loading: Stages, COPY INTO & File Formats

Master Snowflake data loading with stages, COPY INTO, and file formats.

N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Drawn from code that ran under real load.

Follow
Production
production tested
July 17, 2026
last updated
2,439
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of SQL (SELECT, CREATE TABLE)
  • Snowflake account with appropriate permissions (CREATE STAGE, CREATE FILE FORMAT, INSERT on tables)
  • Access to data files (CSV, JSON, Parquet) in a stage or cloud storage
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use internal or external stages to stage data files before loading.
  • COPY INTO command loads data from a stage into a table.
  • File formats (CSV, JSON, Parquet) define how files are parsed.
  • Validate files with VALIDATION_MODE before full load.
  • Use transformations during COPY for on-the-fly data cleaning.
✦ Definition~90s read
What is Data Loading?

Snowflake data loading is the process of moving data from external files into Snowflake tables using stages, file formats, and the COPY INTO command.

Think of Snowflake data loading like moving boxes into a warehouse.
Plain-English First

Think of Snowflake data loading like moving boxes into a warehouse. You first put boxes on a loading dock (stage), then use a forklift (COPY INTO) to move them onto shelves (tables). File formats are like instructions on how to open each box (CSV, JSON, etc.).

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

Loading data into Snowflake is a fundamental task for any data engineer or analyst. Whether you're migrating from a legacy system, ingesting streaming data, or loading daily exports, understanding Snowflake's data loading mechanics is crucial for performance, cost, and reliability. Snowflake offers a powerful and flexible loading pipeline: stages, file formats, and the COPY INTO command. Stages act as temporary storage locations for your data files, either managed by Snowflake (internal) or in cloud storage (external). File formats define the structure and parsing rules for your data (CSV, JSON, Parquet, etc.). The COPY INTO command is the workhorse that loads data from a stage into a table, with options for error handling, validation, and transformation. In this tutorial, you'll learn how to set up stages, define file formats, and use COPY INTO to load data efficiently. We'll cover production-ready examples, common pitfalls, and debugging techniques to ensure your data loads are robust and performant.

Understanding Stages in Snowflake

Stages are logical locations where data files are stored before loading into tables. Snowflake supports two types: internal stages (managed by Snowflake) and external stages (pointing to cloud storage like S3, Azure Blob, or GCS). Internal stages come in three flavors: user stages (for individual users), table stages (automatically created per table), and named stages (reusable across tables). Named stages are best for production because they allow centralized management of file format and encryption settings. To create a named internal stage: CREATE OR REPLACE STAGE my_stage;. For external stages, you need to provide cloud storage credentials: CREATE OR REPLACE STAGE my_ext_stage URL='s3://my-bucket/path/' CREDENTIALS=(AWS_KEY_ID='...' AWS_SECRET_KEY='...');. Stages can also have directory tables for listing files. Use LIST @my_stage; to see files. Stages are the entry point for all data loading operations.

create_stage.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
-- Create an internal named stage
CREATE OR REPLACE STAGE my_stage
  FILE_FORMAT = my_csv_format
  ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE');

-- Create an external stage pointing to S3
CREATE OR REPLACE STAGE my_ext_stage
  URL = 's3://my-bucket/data/'
  CREDENTIALS = (AWS_KEY_ID = 'AKIA...' AWS_SECRET_KEY = '...')
  FILE_FORMAT = my_json_format;

-- List files in the stage
LIST @my_stage;
Output
+---------------------------------------+------+----------------------------------+
| name | size | md5 |
|---------------------------------------+------+----------------------------------|
| my_stage/data_2025-01-01.csv | 1024 | abc123... |
+---------------------------------------+------+----------------------------------+
💡Use Named Stages for Production
📊 Production Insight
External stages require proper IAM roles or keys. Rotate credentials regularly and use Snowflake's storage integrations for better security.
🎯 Key Takeaway
Stages are storage locations for data files. Use named stages for production to centralize configuration.

Defining File Formats

File formats tell Snowflake how to parse your data files. You can define them as standalone objects or inline in COPY INTO. Common formats: CSV, JSON, Avro, Parquet, ORC, and XML. For CSV, key options include FIELD_DELIMITER, SKIP_HEADER, NULL_IF, and FIELD_OPTIONALLY_ENCLOSED_BY. For JSON, you can specify STRIP_OUTER_ARRAY or allow duplicate columns. Parquet and Avro are self-describing, so fewer options are needed. Create a file format: CREATE OR REPLACE FILE FORMAT my_csv_format TYPE = CSV FIELD_DELIMITER = ',' SKIP_HEADER = 1 NULL_IF = ('NULL', '') EMPTY_FIELD_AS_NULL = TRUE;. You can also set DATE_FORMAT, TIME_FORMAT, and TIMESTAMP_FORMAT to handle date/time strings. File formats can be shared across stages and COPY commands, ensuring consistency. Always test your file format with a SELECT query on stage files: SELECT $1, $2 FROM @my_stage (FILE_FORMAT => my_csv_format) LIMIT 10;.

file_formats.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- CSV file format with header and null handling
CREATE OR REPLACE FILE FORMAT my_csv_format
  TYPE = CSV
  FIELD_DELIMITER = ','
  SKIP_HEADER = 1
  NULL_IF = ('NULL', 'N/A', '')
  EMPTY_FIELD_AS_NULL = TRUE
  FIELD_OPTIONALLY_ENCLOSED_BY = '"'
  ERROR_ON_COLUMN_COUNT_MISMATCH = FALSE;

-- JSON file format
CREATE OR REPLACE FILE FORMAT my_json_format
  TYPE = JSON
  STRIP_OUTER_ARRAY = TRUE;

-- Parquet file format (minimal options)
CREATE OR REPLACE FILE FORMAT my_parquet_format
  TYPE = PARQUET;

-- Test parsing a file
SELECT $1, $2, $3 FROM @my_stage (FILE_FORMAT => my_csv_format) LIMIT 5;
Output
+------+------+------+
| $1 | $2 | $3 |
|------+------+------|
| id | name | age |
| 1 | Alice| 30 |
| 2 | Bob | 25 |
+------+------+------+
⚠ Beware of Header Rows
📊 Production Insight
For CSV files with variable columns, set ERROR_ON_COLUMN_COUNT_MISMATCH = FALSE to allow extra columns (they will be ignored).
🎯 Key Takeaway
File formats define parsing rules. Create reusable file format objects to ensure consistency across loads.

The COPY INTO Command: Basics

COPY INTO is the primary command to load data from a stage into a table. Basic syntax: COPY INTO my_table FROM @my_stage FILE_FORMAT = (FORMAT_NAME = my_csv_format);. You can also specify a list of files: FILES = ('file1.csv', 'file2.csv') or a pattern: PATTERN = '.2025.\.csv'. COPY INTO automatically tracks which files have been loaded to prevent duplicates (using metadata). To force reload, use FORCE = TRUE. You can also load into a specific subset of columns: COPY INTO my_table (col1, col2) FROM ... . Error handling options: ON_ERROR = 'CONTINUE' (skip bad rows), 'SKIP_FILE' (skip entire file), or 'ABORT_STATEMENT' (default). Use VALIDATION_MODE = RETURN_ERRORS to preview errors without loading. After a COPY, you can query the result with RESULT_SCAN. Example: COPY INTO my_table FROM @my_stage VALIDATION_MODE = RETURN_ERRORS; SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));.

copy_into_basics.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- Simple COPY INTO with file format
COPY INTO my_table
  FROM @my_stage
  FILE_FORMAT = (FORMAT_NAME = my_csv_format);

-- Load specific files with pattern
COPY INTO my_table
  FROM @my_stage
  PATTERN = '.*2025.*\.csv'
  FILE_FORMAT = (FORMAT_NAME = my_csv_format);

-- Validate without loading
COPY INTO my_table
  FROM @my_stage
  FILE_FORMAT = (FORMAT_NAME = my_csv_format)
  VALIDATION_MODE = RETURN_ERRORS;

-- Check load history
SELECT * FROM INFORMATION_SCHEMA.LOAD_HISTORY
  WHERE TABLE_NAME = 'MY_TABLE'
  ORDER BY LAST_LOAD_TIME DESC;
Output
+------------------+--------+---------+
| file | status | rows |
|------------------+--------+---------|
| data_2025-01.csv | LOADED | 1000 |
+------------------+--------+---------+
🔥Idempotent Loads
📊 Production Insight
Always monitor load history via INFORMATION_SCHEMA.LOAD_HISTORY to track which files were loaded and when.
🎯 Key Takeaway
COPY INTO loads data from a stage into a table. Use VALIDATION_MODE to test before production loads.

Transformations During COPY

Snowflake allows you to transform data during the COPY operation using SELECT expressions. This is powerful for cleaning, casting, or deriving columns on the fly. Syntax: COPY INTO my_table (col1, col2) FROM (SELECT $1::INT, $2::VARCHAR FROM @my_stage) FILE_FORMAT = (FORMAT_NAME = my_csv_format);. You can also use functions like TRY_CAST, NULLIF, or CASE statements. For JSON data, you can extract fields: COPY INTO my_table FROM (SELECT $1:id::INT, $1:name::VARCHAR FROM @my_stage) FILE_FORMAT = (FORMAT_NAME = my_json_format);. Transformations can also include subqueries referencing other tables, but be cautious about performance. Use transformations to handle data quality issues like trimming whitespace, converting date formats, or mapping codes to descriptions. This reduces the need for post-load cleanup.

copy_with_transforms.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- Transform CSV data during load
COPY INTO my_table (id, name, age)
FROM (
  SELECT
    $1::INT AS id,
    TRIM($2) AS name,
    NULLIF($3, '')::INT AS age
  FROM @my_stage
)
FILE_FORMAT = (FORMAT_NAME = my_csv_format);

-- Extract fields from JSON
COPY INTO my_table (id, name)
FROM (
  SELECT
    $1:id::INT,
    $1:name::VARCHAR
  FROM @my_stage
)
FILE_FORMAT = (FORMAT_NAME = my_json_format);

-- Use CASE for data cleaning
COPY INTO my_table (status)
FROM (
  SELECT
    CASE WHEN $1 = 'active' THEN 'A'
         WHEN $1 = 'inactive' THEN 'I'
         ELSE 'U'
    END
  FROM @my_stage
)
FILE_FORMAT = (FORMAT_NAME = my_csv_format);
Output
+------+-------+-----+
| id | name | age |
|------+-------+-----|
| 1 | Alice | 30 |
| 2 | Bob | 25 |
+------+-------+-----+
💡Use TRY_CAST for Safe Conversions
📊 Production Insight
Transformations can impact performance. For large files, consider staging data as-is and then using a separate transformation step.
🎯 Key Takeaway
Transform data during COPY to clean and structure it on the fly, reducing post-load processing.

Error Handling and Validation

Production data loads often encounter errors: malformed files, type mismatches, or constraint violations. Snowflake provides several mechanisms to handle errors gracefully. The ON_ERROR option in COPY INTO controls behavior: 'ABORT_STATEMENT' (default) stops on first error, 'SKIP_FILE' skips the entire file, 'CONTINUE' skips bad rows and logs them. Use VALIDATION_MODE to test files before loading: RETURN_ERRORS returns all errors, RETURN_ALL_ERRORS includes warnings, and RETURN_ROW_COUNT returns row counts. After a load, you can query the result using RESULT_SCAN to see rejected rows. For ongoing monitoring, query INFORMATION_SCHEMA.LOAD_HISTORY and INFORMATION_SCHEMA.COPY_ERRORS. Example: SELECT * FROM TABLE(VALIDATE(my_table, job_id=>'...')). You can also set up notifications for load failures using Snowflake's event notifications.

error_handling.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- Use ON_ERROR to skip bad rows
COPY INTO my_table
  FROM @my_stage
  FILE_FORMAT = (FORMAT_NAME = my_csv_format)
  ON_ERROR = 'CONTINUE';

-- Validate and see errors
COPY INTO my_table
  FROM @my_stage
  FILE_FORMAT = (FORMAT_NAME = my_csv_format)
  VALIDATION_MODE = RETURN_ERRORS;

-- View rejected rows after a load
SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));

-- Query load history
SELECT * FROM INFORMATION_SCHEMA.LOAD_HISTORY
  WHERE TABLE_NAME = 'MY_TABLE' AND STATUS = 'FAILED';
Output
+------------------+--------+----------------------+
| file | status | error |
|------------------+--------+----------------------|
| bad_data.csv | FAILED | Numeric value 'abc' |
+------------------+--------+----------------------+
⚠ Don't Ignore Errors
📊 Production Insight
Set up automated alerts on LOAD_HISTORY to detect failed loads quickly.
🎯 Key Takeaway
Use VALIDATION_MODE and ON_ERROR to handle load errors. Monitor load history for failed loads.

Performance Best Practices

Loading large datasets efficiently requires attention to file size, parallelism, and warehouse size. Snowflake recommends files between 10-100 MB (compressed) for optimal performance. Larger files should be split into smaller chunks. Use multiple COPY statements in parallel to load different files simultaneously. You can also use a larger warehouse (e.g., X-SMALL to MEDIUM) to speed up loads, but note that COPY INTO automatically uses parallel processing regardless of warehouse size. For extremely large loads, consider using Snowpipe for continuous ingestion. Also, avoid loading into tables with many indexes or constraints during bulk loads; drop and recreate them after. Use the PURGE option to automatically delete files from the stage after successful load: COPY INTO ... PURGE = TRUE. Finally, compress files (gzip, bzip2, etc.) to reduce network transfer time.

performance_tips.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- Use a larger warehouse for faster loads
ALTER WAREHOUSE my_wh SET WAREHOUSE_SIZE = 'MEDIUM';

-- Load multiple files in parallel (separate sessions)
-- Session 1
COPY INTO my_table FROM @my_stage PATTERN = '.*part1.*';
-- Session 2
COPY INTO my_table FROM @my_stage PATTERN = '.*part2.*';

-- Purge files after successful load
COPY INTO my_table FROM @my_stage PURGE = TRUE;

-- Check load performance
SELECT * FROM INFORMATION_SCHEMA.LOAD_HISTORY
  WHERE TABLE_NAME = 'MY_TABLE'
  ORDER BY BYTES_LOADED DESC;
Output
+------------------+--------------+--------------+
| file | bytes_loaded | time_seconds |
|------------------+--------------+--------------|
| large_file.csv | 500000000 | 120 |
+------------------+--------------+--------------+
💡Compress Your Files
📊 Production Insight
For continuous loading, use Snowpipe which automatically loads files as they arrive in the stage.
🎯 Key Takeaway
Optimize load performance by splitting files, using appropriate warehouse size, and compressing data.

Loading Semi-Structured Data (JSON, Parquet)

Snowflake excels at loading semi-structured data like JSON, Avro, Parquet, and ORC. For JSON, you can load the entire document into a VARIANT column or extract fields during COPY. Parquet and Avro are columnar formats that preserve schema; Snowflake can infer the schema automatically. Example: CREATE TABLE my_json_table (data VARIANT); COPY INTO my_json_table FROM @my_stage FILE_FORMAT = (FORMAT_NAME = my_json_format);. Then query using dot notation: SELECT data:id, data:name FROM my_json_table. For Parquet, you can load directly into a table with columns matching the Parquet schema. Use MATCH_BY_COLUMN_NAME to align columns by name instead of position: COPY INTO my_table FROM @my_stage FILE_FORMAT = (FORMAT_NAME = my_parquet_format) MATCH_BY_COLUMN_NAME = 'CASE_INSENSITIVE';. This is especially useful when Parquet files have columns in different order.

semi_structured.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- Load JSON into a VARIANT column
CREATE OR REPLACE TABLE raw_json (data VARIANT);
COPY INTO raw_json
  FROM @my_stage
  FILE_FORMAT = (FORMAT_NAME = my_json_format);

-- Query JSON data
SELECT data:id::INT AS id, data:name::VARCHAR AS name
FROM raw_json;

-- Load Parquet with column matching
CREATE OR REPLACE TABLE parquet_table (id INT, name VARCHAR);
COPY INTO parquet_table
  FROM @my_stage
  FILE_FORMAT = (FORMAT_NAME = my_parquet_format)
  MATCH_BY_COLUMN_NAME = 'CASE_INSENSITIVE';
Output
+------+-------+
| id | name |
|------+-------|
| 1 | Alice |
| 2 | Bob |
+------+-------+
🔥VARIANT vs. Structured Columns
📊 Production Insight
For large JSON files, consider flattening nested structures using LATERAL FLATTEN during COPY to load into normalized tables.
🎯 Key Takeaway
Semi-structured data can be loaded as-is or transformed. Use MATCH_BY_COLUMN_NAME for Parquet to handle column order changes.
● Production incidentPOST-MORTEMseverity: high

The Midnight Load Failure: A Snowflake Horror Story

Symptom
Data loads succeeded but rows were missing. No errors reported.
Assumption
The developer assumed CSV files had headers and used default settings.
Root cause
The CSV files had headers, but the COPY INTO command did not use SKIP_HEADER=1, causing the header row to be loaded as data.
Fix
Added SKIP_HEADER=1 to the file format definition and re-loaded the data.
Key lesson
  • Always validate your data after loading with row counts or sample queries.
  • Explicitly define file format options like SKIP_HEADER, FIELD_OPTIONALLY_ENCLOSED_BY, etc.
  • Use VALIDATION_MODE before full load to catch parsing issues.
  • Monitor load history using INFORMATION_SCHEMA.LOAD_HISTORY.
  • Set up alerts for data quality checks.
Production debug guideSymptom to Action4 entries
Symptom · 01
COPY INTO succeeds but no rows loaded
Fix
Check if file is empty or if file format is incorrect. Use LIST to verify files in stage.
Symptom · 02
Rows loaded but data is shifted or null
Fix
Verify column order in table matches file. Use VALIDATION_MODE to see errors.
Symptom · 03
Load fails with 'unsupported file format'
Fix
Ensure file format type matches file extension. For compressed files, specify COMPRESSION.
Symptom · 04
Load is very slow
Fix
Check file size: split large files (e.g., >100MB) into smaller chunks. Use multiple COPY statements.
★ Quick Debug Cheat SheetCommon symptoms and immediate actions for Snowflake data loading issues.
No rows loaded
Immediate action
Check file existence and format
Commands
LIST @my_stage;
SELECT * FROM @my_stage (FILE_FORMAT => my_format) LIMIT 10;
Fix now
Re-create file format with correct options.
Data shifted columns+
Immediate action
Verify column order and file format
Commands
SELECT $1, $2, $3 FROM @my_stage (FILE_FORMAT => my_format) LIMIT 5;
DESC TABLE my_table;
Fix now
Adjust column order in COPY INTO or use file format with PARSE_HEADER.
Load fails with errors+
Immediate action
Use VALIDATION_MODE
Commands
COPY INTO my_table FROM @my_stage VALIDATION_MODE = RETURN_ERRORS;
SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Fix now
Fix file format or data according to errors.
FeatureInternal StageExternal Stage
Storage LocationSnowflake-managedYour cloud storage (S3, Azure, GCS)
Data ControlLimited (Snowflake handles)Full control over data lifecycle
SecuritySnowflake encryptionYour own encryption and IAM
PerformanceOptimized for SnowflakeDepends on cloud provider
CostStorage costs applyNo additional Snowflake storage cost
Use CaseQuick ad-hoc loadsProduction pipelines with existing storage
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
create_stage.sqlCREATE OR REPLACE STAGE my_stageUnderstanding Stages in Snowflake
file_formats.sqlCREATE OR REPLACE FILE FORMAT my_csv_formatDefining File Formats
copy_into_basics.sqlCOPY INTO my_tableThe COPY INTO Command
copy_with_transforms.sqlCOPY INTO my_table (id, name, age)Transformations During COPY
error_handling.sqlCOPY INTO my_tableError Handling and Validation
performance_tips.sqlALTER WAREHOUSE my_wh SET WAREHOUSE_SIZE = 'MEDIUM';Performance Best Practices
semi_structured.sqlCREATE OR REPLACE TABLE raw_json (data VARIANT);Loading Semi-Structured Data (JSON, Parquet)

Key takeaways

1
Use named stages and file formats for consistent, reusable data loading configurations.
2
Always validate your data with VALIDATION_MODE before full production loads.
3
Transform data during COPY to clean and structure it on the fly.
4
Monitor load history and set up alerts for failed loads.
5
Optimize performance by splitting files, compressing data, and using appropriate warehouse sizes.

Common mistakes to avoid

4 patterns
×

Forgetting to set SKIP_HEADER for CSV files with headers

×

Using default file format without specifying NULL handling

×

Loading large files without splitting

×

Not validating data before full load

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Explain the purpose of stages in Snowflake data loading.
Q02SENIOR
How does COPY INTO handle duplicate files?
Q03SENIOR
What are the options for error handling in COPY INTO?
Q04SENIOR
How can you transform data during a COPY INTO operation?
Q01 of 04JUNIOR

Explain the purpose of stages in Snowflake data loading.

ANSWER
Stages are logical storage locations where data files are staged before loading into tables. They can be internal (managed by Snowflake) or external (pointing to cloud storage). Stages allow you to decouple file storage from loading and provide a centralized point for file format and encryption settings.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between internal and external stages?
02
How do I prevent duplicate data loads?
03
Can I load data from a local file directly?
04
What is the maximum file size for COPY INTO?
05
How do I handle CSV files with different delimiters?
N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Drawn from code that ran under real load.

Follow
Verified
production tested
July 17, 2026
last updated
2,439
articles · all by Naren
🔥

That's Snowflake. Mark it forged?

4 min read · try the examples if you haven't

Previous
Virtual Warehouses: Compute Resources, Sizing, and Auto-Scaling
5 / 33 · Snowflake
Next
SQL: Querying, Filtering, Joins, and Window Functions