---
title: "How to convert CSV to ORC"
date: "2026-06-08"
author: "Al Brown"
category: "Engineering"
excerpt: "Convert a CSV file to columnar ORC with one clickhouse-local command — no upload and no server, the schema is inferred from the CSV and the types are carried into the ORC file."
---

# How to convert CSV to ORC

To convert a CSV file to ORC, use [`clickhouse local`](/resources/engineering/what-is-clickhouse-local). It runs SQL directly on files from the command line, with no server to install. It's part of ClickHouse, so the same query scales to billions of rows when you outgrow your laptop.

Install it with [`clickhousectl`](https://clickhouse.com/docs/install):


```bash
curl https://clickhouse.com/cli | sh   # install clickhousectl
clickhousectl local use latest         # download ClickHouse and put it on your PATH
```

Then point it at the CSV and write the result into ORC:


```bash
clickhouse local -q "SELECT * FROM file('events.csv') INTO OUTFILE 'events.orc' TRUNCATE FORMAT ORC"
```

```response
2026-01-01	1	GB	click	5	1
2026-01-02	2	US	view	6.01	2
2026-01-03	3	DE	signup	7.02	3
2026-01-04	4	FR	purchase	8.03	4
2026-01-05	5	IN	click	9.04	5
```

ClickHouse reads the CSV header for column names, infers types from the data, and streams rows into a typed columnar ORC file. The source file is read in place with no import step, and because it streams, it handles files larger than RAM.

## What you get

The result is a real columnar [ORC file](/resources/engineering/orc-file-format) with the column names from the header and the types ClickHouse inferred from the data. Check both ends with `DESCRIBE`:


```bash
clickhouse local -q "DESCRIBE file('events.csv')"   # source
clickhouse local -q "DESCRIBE file('events.orc')"   # result
```

```response
-- CSV (source):
event_date	Nullable(Date)
event_id	Nullable(Int64)
country	Nullable(String)
action	Nullable(String)
amount	Nullable(Float64)
quantity	Nullable(Int64)
-- ORC (result):
event_date	Nullable(Date32)
event_id	Nullable(Int64)
country	Nullable(String)
action	Nullable(String)
amount	Nullable(Float64)
quantity	Nullable(Int64)
```

Read the ORC straight back, or aggregate on it without a load step:


```bash
clickhouse local -q "SELECT * FROM file('events.orc') LIMIT 5"
```

```response
2026-01-01	1	GB	click	5	1
2026-01-02	2	US	view	6.01	2
2026-01-03	3	DE	signup	7.02	3
2026-01-04	4	FR	purchase	8.03	4
2026-01-05	5	IN	click	9.04	5
```

## Types carry over, with ORC's encoding

This is the information you don't get from a drag-and-drop converter site: the type mapping is explicit and it round-trips. ORC is a typed columnar format, so the inferred CSV types are stored as real ORC types rather than as text. Two mappings are worth knowing up front, because they hold even when you pin the types yourself:

- ORC has no 32-bit `Date` type, so a ClickHouse `Date` is written as `Date32`. The values are identical; only the on-disk width differs.
- ORC integers are signed. An unsigned ClickHouse type such as `UInt32` is stored as the matching signed `Int32`.

You can see this by pinning the schema on the way in. Even when the source columns are declared `Date`, `UInt32`, and `UInt8`, the ORC result reports `Date32`, `Int32`, and `Int8`:


```bash
clickhouse local -q "
SELECT * FROM file('events.csv', 'CSVWithNames',
  'event_date Date, event_id UInt32, country String, action String, amount Float64, quantity UInt8')
INTO OUTFILE 'events_typed.orc' TRUNCATE FORMAT ORC"
clickhouse local -q "DESCRIBE file('events_typed.orc')"
```

```response
event_date	Nullable(Date32)
event_id	Nullable(Int32)
country	Nullable(String)
action	Nullable(String)
amount	Nullable(Float64)
quantity	Nullable(Int8)
```

Pinning the schema is still worth doing when type inference guesses wrong on the CSV — for example when an ID or a ZIP code should stay a string rather than become an integer. Pass the format and an explicit structure as the second and third arguments to `file()`.

## Options worth setting

ORC compresses each column block on write. ClickHouse defaults to LZ4; you can switch the codec with one setting:


```bash
clickhouse local -q "
SELECT * FROM file('events_large.csv')
INTO OUTFILE 'events_large.orc' TRUNCATE FORMAT ORC
SETTINGS output_format_orc_compression_method='zstd'"
```

On the 3,000,000-row sample CSV, the codec choice maps to file size like this:


```response
events_large.csv	123.28 MiB
events_large_none.orc	54.54 MiB
events_large.orc (lz4 default)	19.50 MiB
events_large_zstd.orc	19.50 MiB
```

Even uncompressed, ORC is less than half the size of the CSV because it stores typed columns instead of text. With LZ4 or `zstd` it lands near 20 MiB, roughly a 6x reduction over the source CSV, and `zstd` will usually edge ahead of LZ4 on larger, less repetitive data at a small write-time cost. `none` is useful when another layer already compresses the bytes. Other useful knobs: `output_format_orc_row_index_stride` controls the row-group stride that lets readers skip blocks, and `output_format_orc_string_as_string` keeps ORC string columns as `string` rather than `binary`.

## Convert it in Python instead

If you work in a notebook, [chDB](/resources/engineering/what-is-chdb) is ClickHouse as an in-process Python module. The conversion is the same SQL, no server and no separate binary:


```python
import chdb

chdb.query("SELECT * FROM file('events.csv') INTO OUTFILE 'events.orc' TRUNCATE FORMAT ORC")
print(chdb.query("SELECT count() FROM file('events.orc')", "CSV"), end="")
```

```response
20
```

The `INTO OUTFILE ... FORMAT ORC` clause writes the file directly from the engine, so you skip the pandas-then-`to_orc` round trip and the conversion handles inputs larger than memory. To read ORC back into a DataFrame, see [how to read an ORC file in Python](/resources/engineering/read-orc-file-python).

## How fast is it?

Converting `events_large.csv` (3,000,000 rows, ~123 MiB) to ORC, including parsing the CSV text from scratch every run:


```bash
clickhouse local -q "SELECT * FROM file('events_large.csv') INTO OUTFILE 'events_large.orc' TRUNCATE FORMAT ORC"
```

**~0.50 seconds**, best of three with a warm OS page cache, on an Apple M4 Pro laptop (14 cores, 24 GB RAM). The number can wobble slightly under concurrent load. That is a one-time cost: you convert once, then every later query reads the typed columnar ORC instead of re-parsing CSV text.

## Why convert at all

CSV is convenient to produce but slow to scan repeatedly, because every query re-parses the text and re-infers the types. ORC stores the data column by column, already typed and compressed, so analytical scans read less and skip what they don't need. Parquet is the other common columnar choice and is often a better default for cross-tool interchange; see [convert CSV to Parquet](/resources/engineering/convert-csv-to-parquet) and [Parquet vs CSV](/resources/engineering/parquet-vs-csv). Reach for ORC when your stack expects it, for example a Hive or Spark pipeline standardized on ORC.

> Going the other way? See [how to convert ORC to CSV](/resources/engineering/convert-orc-to-csv).

The same SQL that converts a file on your laptop runs unchanged against a ClickHouse server and [ClickHouse Cloud](https://clickhouse.com/cloud) when the data outgrows one machine — no rewrite.

## Run it yourself

The complete, runnable example lives in the ClickHouse examples repo: `generate.sh` to create the sample CSVs (including the ~123 MiB file used for the timing above), `run.sh` with every command on this page, a chDB `run.py` / `run.ipynb`, and `expected_output.txt`.

[github.com/ClickHouse/examples/tree/main/local-analytics/convert-csv-to-orc](https://github.com/ClickHouse/examples/tree/main/local-analytics/convert-csv-to-orc)

Related: [run SQL on a CSV file](/resources/engineering/run-sql-on-csv-file), [read an ORC file with SQL](/resources/engineering/read-orc-file), and [what is the ORC file format](/resources/engineering/orc-file-format).
