--- layout: docu redirect_from: - /docs/data/partitioning/partitioned_writes - /docs/stable/data/partitioning/partitioned_writes - /docs/current/data/partitioning/partitioned_writes - /docs/lts/data/partitioning/partitioned_writes - /docs/preview/data/partitioning/partitioned_writes title: Partitioned Writes --- ## Examples Write a table to a Hive partitioned dataset of Parquet files: ```sql COPY orders TO 'orders' (FORMAT parquet, PARTITION_BY (year, month)); ``` Write a table to a Hive partitioned dataset of CSV files, allowing overwrites: ```sql 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: ```sql 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]({% link docs/2.0/sql/statements/copy.md %}), the files are written in a [Hive partitioned]({% link docs/2.0/data/partitioning/hive_partitioning.md %}) 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. ```text 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): ```batch 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: ```sql 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: ```sql 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]({% link docs/2.0/data/partitioning/hive_partitioning.md %}) convention. The `PARTITION_PATH` option replaces this with an expression over the `PARTITION_BY` columns that computes the output subdirectory for each partition: ```sql 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__`: ```sql SELECT hive_partition_component('year', 2024); ``` ```text year=2024 ``` ```sql SELECT hive_partition_component('a/b', 'c d'); ``` ```text a%2Fb=c%20d ``` * `path_join(path, ...)` joins its arguments with the platform path separator: ```sql SELECT path_join('dir', 'subdir', 'file.txt'); ``` ```text 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: ```sql 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: ```sql 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]({% link docs/2.0/sql/functions/text.md %}#url_encodestring).