⌘K ctrl+k
Search Shortcut cmd + k | ctrl + k
Partitioned Writes

Examples

Write a table to a Hive partitioned dataset of Parquet files:

COPY orders TO 'orders'
(FORMAT parquet, PARTITION_BY (year, month));

Write a table to a Hive partitioned dataset of CSV files, allowing overwrites:

COPY orders TO 'orders'
(FORMAT csv, PARTITION_BY (year, month), OVERWRITE_OR_IGNORE);

Write a table to a Hive partitioned dataset of GZIP-compressed CSV files, setting explicit data files' extension:

COPY orders TO 'orders'
(FORMAT csv, PARTITION_BY (year, month), COMPRESSION gzip, FILE_EXTENSION 'csv.gz');

Partitioned Writes

When the PARTITION_BY clause is specified for the COPY statement, the files are written in a Hive partitioned folder hierarchy. The target is the name of the root directory (in the example above: orders). The files are written in-order in the file hierarchy. Currently, one file is written per thread to each directory.

orders
├── year=2021
│    ├── month=1
│    │   ├── data_1.parquet
│    │   └── data_2.parquet
│    └── month=2
│        └── data_1.parquet
└── year=2022
     ├── month=11
     │   ├── data_1.parquet
     │   └── data_2.parquet
     └── month=12
         └── data_1.parquet

The values of the partitions are automatically extracted from the data. Note that it can be very expensive to write a larger number of partitions as many files will be created. The ideal partition count depends on how large your dataset is.

To limit the maximum number of files the system can keep open before flushing to disk when writing using PARTITION_BY, use the partitioned_write_max_open_files configuration option (default: 100):

SET partitioned_write_max_open_files = 10;

Bestpractice Writing data into many small partitions is expensive. It is generally recommended to have at least 100 MB of data per partition.

Filename Pattern

By default, files will be named data_0.parquet or data_0.csv. With the flag FILENAME_PATTERN a pattern with {i} or {uuid} can be defined to create specific filenames:

  • {i} will be replaced by an index.
  • {uuid} will be replaced by a 128 bits long UUID.

Write a table to a Hive partitioned dataset of .parquet files, with an index in the filename:

COPY orders TO 'orders'
(FORMAT parquet, PARTITION_BY (year, month), OVERWRITE_OR_IGNORE, FILENAME_PATTERN 'orders_{i}');

Write a table to a Hive partitioned dataset of .parquet files, with unique filenames:

COPY orders TO 'orders'
(FORMAT parquet, PARTITION_BY (year, month), OVERWRITE_OR_IGNORE, FILENAME_PATTERN 'file_{uuid}');

Custom Partition Paths

New As of DuckDB 2.0, the PARTITION_PATH option gives full control over the subdirectory that each partition is written to, replacing the default col=value Hive layout.

By default, the PARTITION_BY clause writes each partition to a col=value subdirectory following the Hive partitioning convention. The PARTITION_PATH option replaces this with an expression over the PARTITION_BY columns that computes the output subdirectory for each partition:

COPY sales TO 'sales'
(FORMAT parquet, PARTITION_BY (region),
    PARTITION_PATH CASE
        WHEN region = 'eu' THEN 'europe'
        ELSE 'region=' || coalesce(region, 'unknown')
    END);

The expression is evaluated once per partition and its result is cast to VARCHAR. The resulting path must be relative (no leading /), non-empty, and must not contain .. segments. PARTITION_PATH requires PARTITION_BY and cannot be combined with HIVE_FILE_PATTERN.

Two helper functions are available to build Hive-style paths inside a PARTITION_PATH expression:

  • hive_partition_component(name, value) returns a URL-encoded name=value component. A NULL value produces __HIVE_DEFAULT_PARTITION__:

      SELECT hive_partition_component('year', 2024);
    
      year=2024
    
      SELECT hive_partition_component('a/b', 'c d');
    
      a%2Fb=c%20d
    
  • path_join(path, ...) joins its arguments with the platform path separator:

      SELECT path_join('dir', 'subdir', 'file.txt');
    
      dir/subdir/file.txt
    

Sorting Rows Within Partitions

New As of DuckDB 2.0, the ORDER_BY copy option sorts the rows before they are written. When combined with PARTITION_BY, the rows are sorted within each partition:

COPY orders TO 'orders'
(FORMAT parquet, PARTITION_BY (year, month), ORDER_BY (amount DESC NULLS LAST));

Each expression in the list can optionally be followed by ASC or DESC and NULLS FIRST or NULLS LAST.

Overwriting

By default the partitioned write will not allow overwriting existing directories. On a local file system, the OVERWRITE and OVERWRITE_OR_IGNORE options remove the existing directories. On remote file systems, overwriting is not supported.

Appending

To append to an existing Hive partitioned directory structure, use the APPEND option:

COPY orders TO 'orders'
(FORMAT parquet, PARTITION_BY (year, month), APPEND);

Using the APPEND option results in a behavior similar to the OVERWRITE_OR_IGNORE, FILENAME_PATTERN '{uuid}' options, but DuckDB performs an extra check for whether the file already exists and then regenerates the UUID in the rare event that it does (to avoid clashes).

Handling Slashes in Columns

To handle slashes in column names, use Percent-Encoding implemented by the url_encode function.

© 2026 DuckDB Foundation, Amsterdam NL
DuckDB Home Code of Conduct Trademark Use Blog