What you will be able to do
- Write COPY INTO <table> against a stage or an external location, using FILES, PATTERN or a path prefix
- Unload a table or query with COPY INTO <location> and control how the output files are split and named
- Choose an ON_ERROR value and know the defaults for COPY and Snowpipe
- Validate staged files before loading, and investigate errors after a load
1.COPY INTO <table>: loading staged files
COPY INTO <table> loads files into a table that already exists. The files can be in a named internal stage, a table stage (@%t), the user stage (@~), a named external stage, or an external location given as a quoted s3://, gcs:// or azure:// URL. Loading requires a running warehouse.
You narrow down which files to load in three ways. A path after the stage name is treated as a case-sensitive prefix. FILES = ( ... ) lists exact file names. PATTERN takes a regular expression. The format comes from FILE_FORMAT = (FORMAT_NAME = ...), from an inline TYPE = ... with options, or from the stage when the stage already has a file format attached.
COPY INTO mytable FROM @my_ext_stage PATTERN='.*sales.*.csv';A second form of the command transforms data during the load: FROM ( SELECT $1, $2 ... FROM @stage ). In the syntax, this form reads only from internal or external stages, not from a bare external URL. It also can't be combined with MATCH_BY_COLUMN_NAME. Two copy options control what happens to files across repeated loads. PURGE = TRUE deletes files after they load successfully; the default leaves them where they are. FORCE = TRUE loads files that haven't changed since an earlier load, which produces duplicate rows.
Checkpoint 1 of 8· Fill the gap
The second statement reloads the same two unchanged files on purpose. Which copy option completes it?
COPY INTO load1 FROM @%load1/data1/ FILES=('test1.csv', 'test2.csv') ? =TRUE;FORCE reloads files that were already loaded, which duplicates rows. PURGE deletes files after loading, and OVERWRITE is an unload option.
Source: docs.snowflake.com2.COPY INTO <location>: unloading to files
Unloading is loading in reverse. COPY INTO @stage or COPY INTO 's3://...' writes data from a table, or from any SELECT (joins included), into files. After that, you download the files with GET from an internal stage, or with the cloud provider's own tools from S3 or Azure. Unloads support only three formats, CSV, JSON and PARQUET. PARTITION BY splits the output by an expression.
| Option | Effect |
|---|---|
| SINGLE | FALSE by default, so output is split across multiple files |
| MAX_FILE_SIZE | Maximum size of each file when unloading to multiple files |
| OVERWRITE | Whether to overwrite existing files at the target |
| INCLUDE_QUERY_ID | Whether to add the query ID to file names |
| VALIDATION_MODE = RETURN_ROWS | The only validation mode for unloads |
File names are generated. If the target path ends in a prefix, Snowflake puts that prefix on every file. Without one, the names start with data_, followed by a suffix that keeps names unique across parallel threads, such as data_stats_0_1_0. When an ad hoc unload writes to S3 or GCS with KMS server-side encryption, KMS_KEY_ID picks the key. If you omit it, the bucket's default KMS key is used.
Checkpoint 2 of 8· Check yourself
An engineer unloads a large table to @mystage/ and sets neither SINGLE nor a filename prefix. What output should they expect?
SINGLE defaults to FALSE, so the output is split across multiple files. Without a prefix, the generated names start with data_.
“If a prefix is not specified, Snowflake prefixes the generated filenames with data_.”Source: docs.snowflake.com
Checkpoint 3 of 8· Exam question
A COPY INTO statement fails partway through loading a batch of 50 CSV files because one file contains a row with a data type mismatch. The engineer wants the load to skip only that one problem file, load every other file completely, and continue processing without stopping the statement. Which `ON_ERROR` value should be set on the COPY command?
Correct answer: A — `SKIP_FILE`, which discards the entire file containing an error and proceeds to load the remaining files in the batch
- A. This option discards the whole file the moment any error is found in it and moves on to the next file, which matches the requirement to skip only the problem file while still loading every other file in full.
- B. This option loads the good rows from every file, including partial rows from the bad file, which is not what was asked since the requirement was to skip that entire file rather than partially load it.
- C. This is indeed the bulk-load default and it does halt the statement on the first error, which is the opposite of the requirement to keep processing the remaining 49 files.
- D. This threshold variant only discards a file once its error rate crosses the given percentage, so rows in a lightly-affected bad file would still be partially loaded instead of the file being skipped outright.
3.ON_ERROR: what a load does when a row is bad
ON_ERROR is a copy option for loading only. It controls what happens when a file contains bad rows. If you set it to CONTINUE, the result reports only one error per file. To see the rest, compare ROWS_PARSED with ROWS_LOADED, or use validation. SKIP_FILE reads the whole file into a buffer whether it has errors or not, so it is slower than the other two options. Skipping a large file over a handful of bad rows also wastes credits.
| Value | Behaviour |
|---|---|
| CONTINUE | Keep loading the file; at most one error message per file is returned |
| SKIP_FILE | Skip a file when an error is found; buffers the whole file |
| SKIP_FILE_<num> | Skip a file when its error rows equal or exceed <num> |
| 'SKIP_FILE_<num>%' | Skip a file when its error-row percentage exceeds <num> |
| ABORT_STATEMENT | Stop the load if any error is found; the default for bulk COPY |
Checkpoint 4 of 8· Match them up
Match each ON_ERROR setting to its behaviour
Tap a term, then the definition that fits it.
A plain number in SKIP_FILE_<num> counts error rows, and a quoted value with % sets a percentage. CONTINUE never skips anything, and ABORT_STATEMENT stops the statement.
“Skip a file when the number of error rows found in the file is equal to or exceeds the specified number.”Source: docs.snowflake.com
The result of a COPY statement has one row per file. If a monitoring job only cares about failures, set RETURN_FAILED_ONLY = TRUE so the result contains only the files that failed to load. The default is FALSE.
Checkpoint 5 of 8· Check yourself
A COPY loads 200 files, and a script should see only the files that failed in the statement's result. Which option does this?
RETURN_FAILED_ONLY limits the result rows to files that failed. ON_ERROR changes how the load behaves, not what it reports. VALIDATION_MODE loads nothing at all.
“Boolean that specifies whether to return only files that have failed to load in the statement result.”Source: docs.snowflake.com
Sources1
4.Validating before and after a load, and reading load history
You can check files without loading them. VALIDATION_MODE makes COPY parse the files and return results without loading any data. RETURN_<n>_ROWS checks the first n rows and fails on the first error it hits. RETURN_ERRORS and RETURN_ALL_ERRORS list errors with the file, line, column and error code. In the syntax, the transformation form of COPY doesn't list VALIDATION_MODE. The troubleshooting guide pairs validation with an unload, so the rejected records end up in a file you can fix:
COPY INTO mytable
FROM @mystage/myfile.csv.gz
VALIDATION_MODE=RETURN_ALL_ERRORS;
SET qid=last_query_id();
COPY INTO @mystage/errors/load_errors.txt FROM (SELECT rejected_record FROM TABLE(result_scan($qid)));Checkpoint 6 of 8· Put it in order
Put the error-capture statements in the order they must run
- 1.Run COPY INTO mytable with VALIDATION_MODE=RETURN_ALL_ERRORS
- 2.Unload rejected_record from result_scan($qid) to a file on the stage
- 3.Store the validation query's ID with SET qid=last_query_id()
LAST_QUERY_ID only refers to the validation query if it runs immediately afterwards, and RESULT_SCAN needs that ID.
“Note that the statements in this section must be run in succession in order to retrieve the applicable records using the LAST_QUERY_ID function.”Source: docs.snowflake.com
After a load, the VALIDATE table function returns every error from an earlier COPY, not just the first. Pass it JOB_ID => '_last' or a query ID. It returns nothing for loads that ran with ABORT_STATEMENT, which is the default, and it fails for loads that transformed data with SELECT.
For history, Information Schema LOAD_HISTORY covers COPY loads from the last 14 days, up to 10,000 rows. The Account Usage view goes back 365 days. Neither includes Snowpipe loads. A load stopped by ABORT_STATEMENT leaves no record in either.
Checkpoint 7 of 8· Check yourself
A load using the default ON_ERROR failed. The engineer runs SELECT * FROM TABLE(VALIDATE(t1, JOB_ID => '_last')) to list every error. What do they get?
VALIDATE returns nothing for statements that ran with ABORT_STATEMENT, the default. Use VALIDATION_MODE before loading, or load with CONTINUE, if you need to see all errors.
“The validation returns no results for COPY statements that specify ON_ERROR = ABORT_STATEMENT (default value).”Source: docs.snowflake.com
Checkpoint 8 of 8· Exam question
A company stores raw event files in an Amazon S3 bucket and wants Snowflake to load them with `COPY INTO` without embedding the AWS access key and secret key in the `CREATE STAGE` statement. Which configuration should the engineer use for the external stage's authentication?
Correct answer: A — Create a storage integration object bound to an AWS IAM role, then reference `STORAGE_INTEGRATION = <name>` when creating the external stage.
- A. A storage integration is a Snowflake object that establishes a trust relationship with an IAM role and is designed exactly to remove the need for embedding static AWS credentials in the stage definition, satisfying the requirement.
- B. Supplying `CREDENTIALS` with an access key and secret key is the direct alternative to storage integrations, but it embeds long-lived static keys in the stage object, which is precisely what the requirement wants to avoid.
- C. Bucket policies alone cannot authenticate Snowflake's compute to the bucket without either a storage integration or credentials configured on the stage, so this does not produce a working external stage.
- D. Snowsight session variables are not a supported authentication mechanism for `CREATE STAGE`, and this approach would still hold static keys in the session rather than eliminating them.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Bulk COPY INTO <table> skips bad files by default, the same way Snowpipe does.Why is that wrong?
The defaults differ. Bulk COPY uses ABORT_STATEMENT and Snowpipe uses SKIP_FILE.
Covered in ON_ERROR: what a load does when a row is bad
2.After a default COPY fails, the VALIDATE function will list all the errors.Why is that wrong?
VALIDATE returns nothing for ABORT_STATEMENT loads, and ABORT_STATEMENT is the default. Use VALIDATION_MODE before loading instead.
Covered in Validating before and after a load, and reading load history
3.Information Schema LOAD_HISTORY shows every file ever loaded into a table, Snowpipe loads included.Why is that wrong?
It covers COPY INTO loads from the last 14 days, it excludes Snowpipe, and aborted loads leave no record in it.
Covered in Validating before and after a load, and reading load history
Practise it for real
Load a JSON file through a named internal stage that carries a file format, validating the file before the real load
1.Run CREATE OR REPLACE FILE FORMAT json_format TYPE = 'JSON' STRIP_OUTER_ARRAY = TRUE;
Why: If the documents are wrapped in an outer array, stripping it makes each document its own row.
You should see: A file format json_format exists in the current schema.
2.Run CREATE OR REPLACE STAGE mystage FILE_FORMAT = json_format;
Why: There is no URL, so this creates an internal stage, and later COPY statements inherit its format.
You should see: An internal stage mystage that uses SNOWFLAKE_FULL encryption by default.
3.From SnowSQL, run PUT file:///tmp/sales.json @mystage AUTO_COMPRESS=TRUE; then CREATE OR REPLACE TABLE house_sales (src VARIANT);
Why: PUT moves the local file into the internal stage, and the VARIANT column holds each JSON document.
You should see: LIST @mystage shows sales.json.gz.
4.Run COPY INTO house_sales FROM @mystage/sales.json.gz VALIDATION_MODE = 'RETURN_ERRORS';
Why: This checks the file without loading anything.
You should see: An empty result if the file is clean, or a list of errors. The table stays empty either way.
5.Run COPY INTO house_sales FROM @mystage/sales.json.gz; then SELECT * FROM house_sales;
Why: This is the real load. ON_ERROR is left at its default, ABORT_STATEMENT.
You should see: One row per JSON document in the src column.
Stuck? Get a nudge
Run the same COPY a second time and check how many rows load. Then add FORCE = TRUE and compare.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Loads data from files to an existing table.”
↩︎ COPY INTO <table>: loading staged files“Snowflake treats the path value as a prefix and performs a prefix match against the file name in the external location.”
↩︎ COPY INTO <table>: loading staged files“By default, COPY does not purge loaded files from the location.”
↩︎ COPY INTO <table>: loading staged files“The COPY statement returns an error message for a maximum of one error found per data file.”
↩︎ ON_ERROR: what a load does when a row is bad“SKIP_FILE is slower than either CONTINUE or ABORT_STATEMENT.”
↩︎ ON_ERROR: what a load does when a row is bad“Default:Bulk loading using COPY:ABORT_STATEMENTSnowpipe:SKIP_FILE”
↩︎ Exam trap 1“Add FORCE = TRUE to a COPY command to reload (duplicate) data from a set of staged data files that have not changed”
↩︎ Prediction“Default:Bulk loading using COPY:ABORT_STATEMENTSnowpipe:SKIP_FILE”
↩︎ Prediction“Skip a file when the number of error rows found in the file is equal to or exceeds the specified number.”
↩︎ Checkpoint“Boolean that specifies whether to return only files that have failed to load in the statement result.”
↩︎ Checkpoint - 2.
“Loading data requires a warehouse.”
↩︎ COPY INTO <table>: loading staged files - 3.
“The files can then be downloaded from the stage/location using the GET command.”
↩︎ COPY INTO <location>: unloading to files - 4.
“The default is SINGLE = FALSE (i.e. unload into multiple files).”
↩︎ COPY INTO <location>: unloading to files“SELECT queries in COPY statements support the full syntax and semantics of Snowflake SQL queries, including JOIN clauses”
↩︎ COPY INTO <location>: unloading to files“If a prefix is not specified, Snowflake prefixes the generated filenames with data_.”
↩︎ Checkpoint - 5.
“No data is loaded when this copy option is specified.”
↩︎ Validating before and after a load, and reading load history“Note that the statements in this section must be run in succession in order to retrieve the applicable records using the LAST_QUERY_ID function.”
↩︎ Checkpoint - 6.
“returns all the errors encountered during the load, rather than just the first error.”
↩︎ Validating before and after a load, and reading load history“The validation returns no results for COPY statements that specify ON_ERROR = ABORT_STATEMENT (default value).”
↩︎ Exam trap 2“The validation returns no results for COPY statements that specify ON_ERROR = ABORT_STATEMENT (default value).”
↩︎ Checkpoint - 7.
“No record is added if the transaction is rolled back, for example, or if the ON_ERROR = ABORT_STATEMENT copy option is included”
↩︎ Validating before and after a load, and reading load history“This view does not return the history of data loaded using Snowpipe.”
↩︎ Exam trap 3 - 8.
“within the last 365 days (1 year)”
↩︎ Validating before and after a load, and reading load history