---
title: "How to convert TSV to CSV"
date: "2026-06-08"
author: "Al Brown"
category: "Engineering"
excerpt: "Convert a tab-separated file to comma-separated from your terminal with clickhouse-local — one command, header preserved, types inferred, no upload and no row limit."
---

# How to convert TSV to CSV

To convert a TSV file to CSV, 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 write the result out with the CSV format:


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

```response
"event_date","event_id","country","action","amount","qty"
"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
```

`file('events.tsv')` is read in place with no import step: ClickHouse picks `TSVWithNames` from the extension, takes column names from the first row, and infers types from the data. `INTO OUTFILE ... FORMAT CSVWithNames` writes those same rows back as comma-separated values with a header, quoting string fields and leaving numbers bare.

## The types survive the round trip

This is the difference between a real conversion and a find-and-replace of tabs with commas. ClickHouse parses the TSV into typed columns, so it knows `amount` is a float and `event_date` is a date. `DESCRIBE` the output file to see the schema it carried across:


```bash
clickhouse local -q "DESCRIBE file('events.csv')"
```

```response
event_date	Nullable(Date)
event_id	Nullable(Int64)
country	Nullable(String)
action	Nullable(String)
amount	Nullable(Float64)
qty	Nullable(Int64)
```

A textual tab-to-comma swap cannot do this, and it breaks the moment a field contains a comma: that field needs quoting in CSV, and ClickHouse quotes it for you. Both formats are described in [what is a TSV file](/resources/engineering/what-is-a-tsv-file).

## Options that matter

This is the information you don't get from an online "TSV to CSV" converter, which uploads your file, gives you one fixed output, and caps the size.

`CSVWithNames` writes the header row. If the consumer expects a bare CSV with no header, use the plain `CSV` format instead:


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

```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
```

If your *input* TSV has no header, read it with the plain `TSV` format and pass a schema, for example `file('events.tsv', 'TSV', 'event_date Date, ...')`.

Because the source is a SQL table, you can also select, rename, cast, or filter columns in the same command instead of doing a second pass:


```bash
clickhouse local -q "
SELECT
  event_date,
  upper(country)        AS country_code,
  amount::Decimal(10,2) AS amount
FROM file('events.tsv')
INTO OUTFILE 'events_clean.csv' TRUNCATE FORMAT CSVWithNames"
```

```response
"event_date","country_code","amount"
"2026-01-01","GB",5
"2026-01-02","US",6.01
"2026-01-03","DE",7.02
```

For a semicolon rather than a comma at the destination, set `format_csv_delimiter` and keep everything else the same: `... FORMAT CSVWithNames SETTINGS format_csv_delimiter=';'`.

One thing to know: this is a row-for-row conversion of flat columns. If your TSV holds a column of raw JSON or another nested structure, it stays as a string in the CSV. CSV has no nested types, so there is nothing lost beyond what CSV itself can't represent.

## Did anything get dropped?

Count both files. They match, which is the quick check that the conversion was clean:


```bash
clickhouse local -q "SELECT count() FROM file('events.tsv')"   # 20
clickhouse local -q "SELECT count() FROM file('events.csv')"   # 20
```

## Reverse direction

Going the other way is the same command with the formats swapped. See [convert CSV to TSV](/resources/engineering/convert-csv-to-tsv). If you'll query the data repeatedly rather than hand it off, skip CSV and go to a columnar format with [convert TSV to Parquet](/resources/engineering/convert-tsv-to-parquet) instead — it's smaller and far faster to scan.

## How fast is it?

On a 3,000,000-row, ~106 MB tab-separated file (`events_large.tsv`), the full TSV-to-CSV conversion runs in:


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

**~0.23 seconds**, best of three with a warm OS page cache, on an Apple M4 Pro laptop (14 cores, 24 GB RAM). That parses every row and re-serialises it; there is no precomputed table. Because it streams, the file does not have to fit in memory, so the same command works on a multi-gigabyte TSV that an upload-based converter would reject.

## Convert in Python with chDB

If you're already in Python, [chDB](/resources/engineering/what-is-chdb) is the same ClickHouse engine in-process. The conversion is the identical SQL:


```python
import chdb

chdb.query(
    "SELECT * FROM file('events.tsv') "
    "INTO OUTFILE 'events_chdb.csv' TRUNCATE FORMAT CSVWithNames"
)
```

```response
"event_date","event_id","country","action","amount","qty"
"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
```

Install with `pip install chdb`. No server, no separate ClickHouse install.

## Run it yourself

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

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

`clickhouse-local` runs the exact same SQL unchanged across dozens of formats and remote sources, and against a ClickHouse server or [ClickHouse Cloud](https://clickhouse.com/cloud) when the data outgrows your machine. Related: [query a TSV file with SQL](/resources/engineering/query-a-tsv-file) and [run SQL on a CSV file](/resources/engineering/run-sql-on-csv-file).
