How to convert a SQL dump file to CSV without restoring the database

By Hammad Rasool · October 13, 2026

Quick answer: a .sql dump already contains your data as text, either as INSERT statements (mysqldump, SQLite .dump) or as COPY … FROM stdin blocks (PostgreSQL's pg_dump). You can turn that text straight into CSV without restoring anything. Use a parser that understands your dump's quoting rules, take the column names from CREATE TABLE, and export one table at a time.

This guide shows what each dump format looks like inside, four ways to get a CSV out of it, and the six problems that silently corrupt the result if you split lines on commas. Every example below was run against a real dump created with SQLite 3.50.6, plus sample files in the documented mysqldump and pg_dump formats. Commands that weren't run for this guide are marked.

What is inside a SQL dump file?

A dump is a script that would rebuild the database: CREATE TABLE statements for the structure, then the rows. How the rows are written depends on the tool that made the dump, and that decides how you extract them.

Dump made byRows are written asColumn names in the data?NULL looks likeQuote escaping
mysqldump (MySQL, MariaDB)Multi-row INSERT … VALUES (…),(…)No, unless --complete-insertNULLBackslash: \', \", \n
pg_dump (PostgreSQL, plain format)COPY … FROM stdin; block, tab-separated, ended by \.Yes, in the COPY line\NBackslash escapes: \t, \n, \\
pg_dump with --insertsOne INSERT per rowNo (yes with --column-inserts)NULLDoubled quotes: ''
SQLite .dumpOne INSERT per rowNoNULL''; line breaks as unistr('…\u000a…') in 3.50+

Sources: mysqldump enables --extended-insert by default through --opt, and only adds column names with --complete-insert (MySQL docs). pg_dump writes data as COPY unless you pass --inserts, which the docs warn makes restoring “very slow” (pg_dump docs). The COPY text format, including \N and the escape list, is defined in the COPY reference.

Real examples of each format

This is part of the real .dump output from SQLite 3.50.6 for a test table with a comma in a name, quotes, a NULL, a line break and an Urdu name:

CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, city TEXT, note TEXT, balance REAL);
INSERT INTO customers VALUES(1,'Khan, Sara','Lahore','said "hi"',12.5);
INSERT INTO customers VALUES(2,'Ali O''Brien',NULL,unistr('line1\u000aline2'),0.0);
INSERT INTO customers VALUES(3,'حنا','Karachi',NULL,-3.25);
INSERT INTO orders VALUES(10,1,99.9000000000000056);

Two things in that output trip up simple scripts. The line break became a unistr() call (the function was added in SQLite 3.50.0), and the stored value 99.9 was written as 99.9000000000000056, all 17 significant digits, so the double round-trips exactly.

A mysqldump-style table, with no column list and backslash escapes:

CREATE TABLE `customers` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `customers` VALUES (1,'O\'Neil'),(2,'Sara \"S\"');

And a pg_dump COPY block. The gaps between values are real tab characters, \N is NULL, and \. ends the data:

COPY public.customers (id, name, note) FROM stdin;
1	Khan, Sara	said "hi"
2	Ali	\N
3	tab\there	line1\nline2
\.

Method 1: paste or open the dump in a browser converter

For dumps up to a few tens of megabytes, the quickest route is the SQL to CSV converter. Open the .sql file or drop it on the page. It reads all three formats above, lists every table with its row count so you can pick one, takes the header from CREATE TABLE when the inserts have no column list, decodes unistr() and COPY escapes, and lets you download the result with a comma, semicolon or tab delimiter. The file is read by your browser and not uploaded.

How it was checked: the converter's CSV for the SQLite test dump above was compared value by value with SQLite's own sqlite3 -csv export of the same table, and every value matched. For the COPY sample, its output was byte-for-byte identical to the Python script in Method 3. The result for the customers table:

id,name,city,note,balance
1,"Khan, Sara",Lahore,"said ""hi""",12.5
2,Ali O'Brien,,"line1
line2",0.0
3,حنا,Karachi,,-3.25

Method 2: SQLite dumps, load into a scratch file

If the dump came from SQLite, “restoring” costs nothing: SQLite has no server, so you can load the dump into a throwaway file and use the CLI's built-in CSV mode. Both commands below were run for this guide:

sqlite3 scratch.db < shop.sql
sqlite3 -csv -header scratch.db "SELECT * FROM customers;" > customers.csv

This also prints REAL values the way people expect (99.9 instead of 99.9000000000000056), because the CLI formats the stored number instead of copying the dump's literal. It only works for SQLite dumps; MySQL- or PostgreSQL-specific syntax such as ENGINE=InnoDB or COPY … FROM stdin will fail. See the SQLite CLI documentation for .mode csv and .once.

Method 3: pg_dump COPY blocks, with a short Python script

COPY data is already one row per line, so it is the easiest format to convert yourself. You only need to split on tabs, turn \N into an empty cell, and undo the backslash escapes. This script uses only the Python standard library, streams the file line by line (so large dumps are fine), and was run against the sample above:

"""Export one table from a plain-text pg_dump file to CSV, without restoring it.

Usage: python pgcopy_to_csv.py dump.sql customers customers.csv
"""
import csv
import re
import sys

ESCAPES = {"b": "\b", "f": "\f", "n": "\n", "r": "\r", "t": "\t", "v": "\v"}


def unescape(field):
    """Decode one field of COPY text format (\\N = NULL, backslash escapes)."""
    if field == r"\N":
        return None

    def repl(m):
        e = m.group(1)
        if e[0] == "x":
            return chr(int(e[1:], 16))
        if e[0] in "01234567":
            return chr(int(e, 8))
        return ESCAPES.get(e, e)

    return re.sub(r"\\(x[0-9a-fA-F]{1,2}|[0-7]{1,3}|.)", repl, field)


def export(dump_path, table, out_path):
    start = re.compile(
        r'^COPY\s+(?:\S+\.)?"?%s"?\s*\((.*)\)\s+FROM\s+stdin;' % re.escape(table)
    )
    with open(dump_path, encoding="utf-8") as src, open(
        out_path, "w", newline="", encoding="utf-8"
    ) as out:
        writer = csv.writer(out)
        inside = False
        for line in src:
            line = line.rstrip("\n")
            if not inside:
                m = start.match(line)
                if m:
                    writer.writerow(c.strip().strip('"') for c in m.group(1).split(","))
                    inside = True
            elif line == r"\.":
                break
            else:
                values = [unescape(v) for v in line.split("\t")]
                writer.writerow("" if v is None else v for v in values)


if __name__ == "__main__":
    export(sys.argv[1], sys.argv[2], sys.argv[3])

Run it as python pgcopy_to_csv.py shop.sql customers customers.csv. If the dump was made with pg_dump -Fc (custom format, not readable text), convert it to plain SQL first with pg_restore -f shop.sql shop.dump. That writes a script and does not touch any database.

Method 4: very large MySQL dumps

mysqldump's multi-row INSERTs are harder to parse safely with a few lines of script, because a single line can hold thousands of rows and values can contain commas, parentheses and escaped quotes. For multi-gigabyte files you have two options.

Option A: a dedicated parser

Open-source projects such as mysqldump-to-csv (Python) and mysqldump2csv (Go) stream a dump and write CSV per table. They were not tested for this guide, so try them on a copy of your data and spot-check the output.

Option B: a throwaway database in Docker

If the dump uses features a parser chokes on (stored procedures, unusual character sets), load it into a temporary server and export from there. These are standard Docker and MySQL client commands, but they were not run for this guide:

# 1. Start a throwaway MySQL server
docker run --name dump-scratch -e MYSQL_ROOT_PASSWORD=scratch -d mysql:8.4

# 2. Load the dump into a new database
docker exec -i dump-scratch mysql -uroot -pscratch -e "CREATE DATABASE shop"
docker exec -i dump-scratch mysql -uroot -pscratch shop < shop.sql

# 3. Export one table as tab-separated text, then delete the container
docker exec dump-scratch mysql -uroot -pscratch --batch -e "SELECT * FROM shop.customers" > customers.tsv
docker rm -f dump-scratch

--batch output is tab-separated and writes NULL as the text NULL, so replace those if empty cells are what you want. Spreadsheets open .tsv files directly, and if the table is going into a README, CSV to Markdown accepts tab-separated input too. Avoid mysqldump --tab for this job: per the MySQL docs it only works when run on the database server itself, the directory must be writable by the server, and the account needs the FILE privilege.

Six things that silently break a dump-to-CSV conversion

  1. No header row. mysqldump and SQLite write INSERT INTO t VALUES (…) with no column names. Take them from the matching CREATE TABLE, or you get anonymous columns.
  2. Splitting on commas. 'Khan, Sara' is one value. Anything that splits VALUES on commas or parentheses shifts columns. The only fix is a real parser that tracks quotes.
  3. Two escaping styles. MySQL escapes quotes with a backslash (O\'Neil), while standard SQL, SQLite and PostgreSQL's --inserts double them (O''Neil). A parser built for one style breaks on the other.
  4. NULL versus empty string. In CSV both usually become an empty cell. If the difference matters to you, export NULL as a marker such as \N and handle it when you import.
  5. Line breaks inside values. They are legal in CSV only inside quotes. A correct converter quotes them; a naive one creates extra rows.
  6. Floating-point literals. A REAL column can come out as 99.9000000000000056 (see the SQLite example). This is the exact stored value, not corruption. Round it in your spreadsheet, or use Method 2 for SQLite. Don't “fix” it automatically for DECIMAL columns, where every digit is significant.

Opening the CSV in Excel? If every row lands in one column, your Excel uses a semicolon list separator. Export with the semicolon delimiter, or use Data → From Text/CSV, which lets you choose the delimiter and UTF-8 encoding. Non-English text such as the Urdu name above can look garbled if Excel guesses the wrong encoding.

Which method should you use?

SituationBest method
One table, file under ~25 MB, any formatMethod 1: browser converter
SQLite dump of any sizeMethod 2: scratch SQLite file
PostgreSQL plain-format dump, largeMethod 3: Python COPY script
PostgreSQL custom-format (-Fc) dumppg_restore -f to plain SQL, then Method 1 or 3
Multi-gigabyte mysqldumpMethod 4: dedicated parser or throwaway Docker database

Frequently asked questions

Can I convert a SQL file to CSV without installing MySQL or PostgreSQL?

Yes. The data is already in the file as text. A browser converter, a short script for COPY blocks, or the SQLite CLI for SQLite dumps all work without a database server.

Why does my CSV have column_1, column_2 instead of real names?

The INSERT statements have no column list, which is the mysqldump and SQLite default. The names are in the dump's CREATE TABLE statement. A converter that reads it, as the SQL to CSV tool does, will use them. Otherwise, re-dump with --complete-insert (MySQL) or --column-inserts (PostgreSQL).

How do I convert only one table from a big dump?

Pick the table in the converter's table menu, or pass the table name to the Python script. If you still have access to the original database, a one-table dump is much smaller: mysqldump db customers or pg_dump -t customers db.

Can I convert a SELECT query to CSV this way?

No. A SELECT statement holds no data, only the question. Run it in a database client and use its export, or use \copy (SELECT …) TO 'file.csv' WITH (FORMAT csv, HEADER) in psql.

Is it safe to paste production data into an online converter?

Only if the tool runs entirely in your browser. The ToolStash converter reads the file locally with JavaScript and makes no upload request. When in doubt, use the script methods offline.

How do I go the other way, CSV to SQL?

Use the CSV to SQL converter to generate CREATE TABLE and INSERT statements, or see the guide on importing CSV into MySQL, PostgreSQL, SQL Server and SQLite for the native bulk loaders.