Skip to main content

COPY INTO

COPY INTO provides a SQL command for bulk loading data files into Paimon tables and exporting table data to files. Supported formats: CSV, JSON, and Parquet.

info

SQL dialect: Paimon's COPY INTO is a Snowflake-style extension (FILE_FORMAT = (TYPE = ...), PATTERN, FORCE, ON_ERROR), not the Databricks COPY INTO form (FILEFORMAT + FORMAT_OPTIONS (...) / COPY_OPTIONS (...)). It implements only a subset of the Snowflake syntax. In particular, ON_ERROR supports ABORT_STATEMENT (default), CONTINUE, and SKIP_FILE; the Snowflake variants SKIP_FILE_<num> and SKIP_FILE_<num>% are not supported.

CSV Import​

COPY INTO table_name [(col1, col2, ...)]
FROM 'source_path'
FILE_FORMAT = (TYPE = CSV [, option = value, ...])
[PATTERN = 'regex']
[FORCE = TRUE|FALSE]
[ON_ERROR = { ABORT_STATEMENT | CONTINUE | SKIP_FILE }]

Basic import:

COPY INTO my_db.my_table
FROM '/data/csv_files/'
FILE_FORMAT = (TYPE = CSV);

Import with explicit column mapping:

-- Only load into specified columns; omitted columns use their DEFAULT value or NULL
COPY INTO my_db.users (id, name)
FROM '/data/new_users/'
FILE_FORMAT = (TYPE = CSV, SKIP_HEADER = 1);

Import with NULL_IF and PATTERN:

COPY INTO my_db.events
FROM '/data/logs/'
FILE_FORMAT = (TYPE = CSV, FIELD_DELIMITER = '|', NULL_IF = ('NULL', '\\N', ''))
PATTERN = '.*\.csv'
FORCE = FALSE;

JSON Import​

COPY INTO table_name [(col1, col2, ...)]
FROM 'source_path'
FILE_FORMAT = (TYPE = JSON [, option = value, ...])
[PATTERN = 'regex']
[FORCE = TRUE|FALSE]
[ON_ERROR = { ABORT_STATEMENT | CONTINUE | SKIP_FILE }]

Basic import:

COPY INTO my_db.my_table
FROM '/data/json_files/'
FILE_FORMAT = (TYPE = JSON);

Import multi-line JSON array:

COPY INTO my_db.events
FROM '/data/events/'
FILE_FORMAT = (TYPE = JSON, MULTI_LINE = TRUE);

JSON columns are matched by column name (not by position), so source field order does not matter.

Parquet Import​

COPY INTO table_name [(col1, col2, ...)]
FROM 'source_path'
FILE_FORMAT = (TYPE = PARQUET [, option = value, ...])
[PATTERN = 'regex']
[FORCE = TRUE|FALSE]
[ON_ERROR = { ABORT_STATEMENT | CONTINUE | SKIP_FILE }]

Basic import:

COPY INTO my_db.my_table
FROM '/data/parquet_files/'
FILE_FORMAT = (TYPE = PARQUET);

Import with PATTERN:

COPY INTO my_db.events
FROM '/data/lake/'
FILE_FORMAT = (TYPE = PARQUET)
PATTERN = '.*\.parquet'
FORCE = FALSE;

Parquet columns are matched by column name (not by position). Extra columns in the source files are ignored; missing columns become NULL.

Write CSV Files​

COPY INTO 'target_path'
FROM { table_name | (SELECT ...) }
FILE_FORMAT = (TYPE = CSV [, option = value, ...])
[OVERWRITE = TRUE|FALSE]

Write with header and overwrite:

COPY INTO '/export/users_backup/'
FROM my_db.users
FILE_FORMAT = (TYPE = CSV, HEADER = TRUE, FIELD_DELIMITER = ',')
OVERWRITE = TRUE;

Write from query:

COPY INTO '/export/active_users/'
FROM (SELECT id, name FROM my_db.users WHERE active = TRUE)
FILE_FORMAT = (TYPE = CSV, HEADER = TRUE);

Write JSON Files​

COPY INTO 'target_path'
FROM { table_name | (SELECT ...) }
FILE_FORMAT = (TYPE = JSON [, option = value, ...])
[OVERWRITE = TRUE|FALSE]

Basic JSON export:

COPY INTO '/export/events_backup/'
FROM my_db.events
FILE_FORMAT = (TYPE = JSON)
OVERWRITE = TRUE;

JSON export from query:

COPY INTO '/export/recent_events/'
FROM (SELECT * FROM my_db.events WHERE event_date > '2024-01-01')
FILE_FORMAT = (TYPE = JSON);

Write Parquet Files​

COPY INTO 'target_path'
FROM { table_name | (SELECT ...) }
FILE_FORMAT = (TYPE = PARQUET [, option = value, ...])
[OVERWRITE = TRUE|FALSE]

Basic Parquet export:

COPY INTO '/export/data_backup/'
FROM my_db.events
FILE_FORMAT = (TYPE = PARQUET)
OVERWRITE = TRUE;

Export with compression:

COPY INTO '/export/data_compressed/'
FROM my_db.events
FILE_FORMAT = (TYPE = PARQUET, COMPRESSION = GZIP)
OVERWRITE = TRUE;

Parquet export from aggregation query:

COPY INTO '/export/summary/'
FROM (SELECT dept, COUNT(*) AS cnt FROM my_db.employees GROUP BY dept)
FILE_FORMAT = (TYPE = PARQUET);

FILE_FORMAT Options​

FILE_FORMAT is required and must include TYPE = CSV, TYPE = JSON, or TYPE = PARQUET.

CSV import options:

OptionDescriptionDefault
TYPEFile format type. CSV, JSON, or PARQUET.(required)
FIELD_DELIMITERColumn delimiter character.,
SKIP_HEADERSkip the first line as header. Only 0 or 1.0
QUOTEQuote character for enclosing fields."
ESCAPEEscape character within quoted fields.\
NULL_IFList of string values to interpret as NULL, e.g. ('NULL', '\\N').(none)
EMPTY_FIELD_AS_NULLTreat empty fields as NULL. TRUE or FALSE.FALSE
COMPRESSIONCompression codec (e.g. GZIP).NONE

JSON import options:

OptionDescriptionDefault
TYPEFile format type. CSV, JSON, or PARQUET.(required)
MULTI_LINEParse multi-line JSON (e.g. JSON arrays or pretty-printed objects).FALSE
NULL_IFList of string values to interpret as NULL.(none)
EMPTY_FIELD_AS_NULLTreat empty string values as NULL.FALSE
COMPRESSIONCompression codec (e.g. GZIP).NONE

Parquet import options:

OptionDescriptionDefault
TYPEFile format type. CSV, JSON, or PARQUET.(required)
COMPRESSIONCompression codec. Usually auto-detected; rarely needed for import.(auto)

CSV write options:

OptionDescriptionDefault
TYPEFile format type. CSV, JSON, or PARQUET.(required)
FIELD_DELIMITERColumn delimiter character.,
HEADERWrite column names as the first line. TRUE or FALSE.FALSE
QUOTEQuote character for enclosing fields."
ESCAPEEscape character within quoted fields.\
COMPRESSIONCompression codec (e.g. GZIP).NONE

JSON write options:

OptionDescriptionDefault
TYPEFile format type. CSV, JSON, or PARQUET.(required)
DATE_FORMATCustom date format pattern.Spark default
TIMESTAMP_FORMATCustom timestamp format pattern.Spark default
COMPRESSIONCompression codec (e.g. GZIP).NONE

Parquet write options:

OptionDescriptionDefault
TYPEFile format type. CSV, JSON, or PARQUET.(required)
COMPRESSIONCompression codec (SNAPPY, GZIP, NONE, etc.).SNAPPY

Import Options​

OptionDescriptionDefault
PATTERNRegex to filter source files by base file name. Only matching files are loaded.(all files)
FORCEFALSE: skip files already loaded (idempotent). TRUE: reload all files.FALSE
ON_ERRORError handling strategy. ABORT_STATEMENT: abort on any error. CONTINUE: skip bad rows and continue loading. SKIP_FILE: skip files that contain errors.ABORT_STATEMENT

File Write Options​

OptionDescriptionDefault
OVERWRITEFALSE: fail if target path exists. TRUE: overwrite existing files.FALSE

Column Mapping​

When an explicit column list is provided (e.g., COPY INTO t (col1, col2) FROM ...):

  • CSV: Columns are mapped positionally to the specified column list.
  • JSON: Columns are matched by name to the specified column list.
  • Parquet: Columns are matched by name to the specified column list.
  • The number of source columns must match the column list length (CSV). For JSON and Parquet, missing fields in the source become NULL.
  • Columns not in the list are filled with their DEFAULT value (if defined in the table schema) or NULL.
  • Non-nullable columns without a default value that are not in the list will cause an error.

When no column list is provided:

  • CSV: Columns are mapped positionally to all writable columns in the target table. The number of CSV columns must match the number of writable columns.
  • JSON: Columns are matched by name to the writable columns. Missing fields in JSON become NULL.

Repeated Imports​

By default (FORCE = FALSE), COPY INTO tracks which files have been successfully loaded. A file is identified by its path, size, and last-modified timestamp.

  • Re-running the same COPY INTO command will skip already-loaded files and return status SKIPPED.
  • If a source file is modified (size or timestamp changes), it becomes eligible for re-loading.
  • FORCE = TRUE bypasses load history and always re-imports all matching files.

Result Output​

Import returns one row per source file:

ColumnTypeDescription
file_nameSTRINGSource file name
statusSTRINGLOADED, PARTIALLY_LOADED, LOAD_FAILED, or SKIPPED
rows_loadedBIGINTNumber of rows written
rows_parsedBIGINTNumber of rows parsed from the file
errors_seenBIGINTNumber of error rows (parse or cast failures)
first_errorSTRINGFirst error message encountered (NULL if no errors)

File write returns a single row:

ColumnTypeDescription
output_pathSTRINGTarget output path
file_countINTNumber of files written
rows_writtenBIGINTTotal rows written

Limitations​

  • CSV column-count mismatch: Rows with fewer or more columns than the target schema are treated as malformed records. With ON_ERROR = CONTINUE, these rows are skipped and counted as errors.
  • Only CSV, JSON, and Parquet formats are supported.
  • SINGLE = TRUE (single-file output) is not supported.
  • File format options must be specified inline in FILE_FORMAT = (...).
  • File listing is non-recursive: only direct files under the source path are processed. Subdirectories are ignored.
  • PATTERN matches the base file name only (not the full path).
  • Concurrent COPY INTO commands targeting the same table may produce duplicate data.
  • SKIP_HEADER only supports values 0 or 1.
  • FROM (...) accepts any read-only query (e.g. SELECT, WITH ... SELECT, VALUES); statements with side effects (e.g. INSERT, INSERT OVERWRITE DIRECTORY, DDL) are rejected.
  • For a FROM (...) export, rows_written is an execution-time statistic counted by a separate pass before the files are written. Because the DataFrame is lazy and not cached, writing re-executes the query a second time; if the query is non-deterministic (e.g. uses rand(), current_timestamp(), or reads a volatile source), the two runs can produce different rows, so rows_written may not match the actual file contents. The result is intentionally not staged, so the export does not consume extra executor disk.

For table-to-table row writes, see SQL Writes. For copying existing Paimon table files, see the copy procedure.