SQL to CSV Converter

Drop a dump, a migration file or three loose INSERT lines into the panel. The parser reads column names off each statement, walks the value tuples one character at a time so a comma inside a quoted string never splits a row, and hands you a CSV per table with the delimiter and NULL token you picked.

SQL to CSV conversion workspace

  • Quote aware parser
  • One CSV per table
  • Runs in your tab
Separator
Empty values
SQL input.sql
CSV outputWaiting for SQL
0Tables found
0Rows in view
0Columns
0Rows patched

A dump is data wearing SQL syntax

An INSERT statement already holds a table: column names in the parentheses after the table name, one row per value tuple after VALUES. Getting to CSV is a matter of reading those two lists correctly and re-quoting the values under CSV rules instead of SQL rules. The whole job sounds like a search and replace until the first row contains an address with a comma in it, at which point the naive version quietly splits one row into two.

Why splitting on commas fails

SQL and CSV both use commas as separators and both use quotes to protect commas inside a field. They disagree on almost everything else, and each disagreement is a row that lands in the wrong column.

SituationSQL writes it asCSV needs
Text with a comma'Berlin, DE'"Berlin, DE"
Apostrophe in text'O''Brien' or 'O\'Brien'O'Brien with no escaping at all
Double quote in text'27" monitor'"27"" monitor"
Missing valueNULLAn empty field, or a token you choose
Line break in text'line one\nline two'A real newline inside a quoted field
Function resultNOW()The literal text, since nothing evaluates it

The parser here reads the VALUES section character by character rather than matching a regular expression against it. It tracks whether it sits inside a quoted string, counts nested parentheses so a CONCAT(a, b) argument list stays one field, and treats a doubled quote as an escaped quote rather than the end of the string. That is the difference between a row surviving and a row shifting one column to the left for the rest of the file.

SQL in
INSERT INTO shops (id, name, note) VALUES
(1, 'Bao & Co, Wan Chai', NULL),(2, 'O''Brien''s', 'has a 27" sign');
CSV out
id,name,note
1,"Bao & Co, Wan Chai",2,O'Brien's,"has a 27"" sign"

Three rewrites happened in those two rows. The comma inside a shop name pulled quotes around that field. The doubled SQL apostrophe collapsed back to one and needed no CSV quoting at all. The inch mark got doubled, because a quote inside a quoted CSV field is written twice. Field two on row two is quoted for the inch mark, field two on row one for the comma, and nothing else is quoted, which is what minimal quoting means.

Statements the parser reads

Dumps come out of a dozen tools and none of them agree on formatting. These forms are all accepted.

Several tables in one file

A real dump holds users, then orders, then line items, each with a different column count. Flattening all of that into one CSV produces a file no spreadsheet will read. Each table gets its own output instead, and the picker at the top right switches between them. Row counts sit next to each name, so a table that came through with two rows when you expected two thousand is visible before you download anything.

Rows are keyed to their table by name, not by position, so an interleaved dump that returns to the users table halfway down still collects every user row into one CSV. The download button names the file after the table you are looking at.

When two INSERT statements target the same table with different column lists, the first list wins and the mismatch is reported in the notes. A row with fewer values than the header is padded with empty fields on the right. A row with more values is cut. Both cases increment the patched counter, and a patched count above zero is worth reading the notes over, since it usually points at a column list that changed partway through the file.

Choosing a separator

Comma is the default and the right answer for anything you feed to a script or a database import. The other three exist because spreadsheets have opinions.

Whichever you pick, the escaping follows it. Switch to semicolon and a value containing a semicolon gets quoted while a value containing a comma stops being quoted. The rule is the same in every case: quote when the field holds the separator, a double quote, a carriage return or a newline.

NULL is not an empty string

CSV has no way to say a value is absent. It has empty fields, and an empty field means the empty string as easily as it means missing. That ambiguity is why three options sit above the editors.

OptionWritten asPick it when
BlankNothing between the separatorsThe target is a spreadsheet, where an empty cell reads naturally
NULLThe four letters NULLYou are eyeballing the file, or reloading it somewhere that maps the word back
\NBackslash then capital NThe file goes into LOAD DATA INFILE or a Postgres COPY, which both read that token as null

One warning about the NULL word option. A row that stores the literal text 'NULL' as a string becomes indistinguishable from a genuine null the moment it lands in the file. If your data has string values spelling out NULL, take the blank option or the backslash token instead.

Getting the file into Excel without mangling it

Excel treats a CSV as a set of hints and applies its own conversions on open. Four of them bite regularly.

The CRLF option matters for the same audience. Windows tooling and the CSV specification both want carriage return plus line feed at the end of a row. Everything on Unix is fine with a bare line feed. Turn CRLF on if the file is headed for Excel or an older Windows import, leave it off for anything else.

What this converter does not do

Nothing you paste is uploaded. The parser, the formatter and the download all run inside this page, so a dump holding customer rows never leaves your machine. Load the page once, cut your connection, and it keeps converting.

Questions about converting SQL to CSV

Quoting, NULL values, multiple tables, dialect differences and the limits of parsing a dump in a browser.

My dump has ten tables. Which one do I get?

All of them, one at a time. Every table found in the file appears in the picker at the top right with its row count next to the name, and switching the picker rewrites the output panel and the download filename. Rows are grouped by table name rather than by where they sit in the file, so a dump that inserts into users, then orders, then back into users still gives you one complete users CSV. If a table name shows a row count far below what you expected, open the notes panel under the editors, since skipped rows are always reported there.

Why are only some of my fields wrapped in quotes?

That is minimal quoting, which is the default. A field gets quotes only when it contains the separator, a double quote character, a carriage return or a newline. Everything else goes out bare, which keeps the file smaller and easier to read. Some import scripts written against a stricter reader want every field quoted regardless, so the Quote every field checkbox switches to that mode. The parsed data is identical either way.

What happens to NULL values?

By default a NULL becomes an empty field, which is what a spreadsheet expects. Two other options sit above the editors. Writing the word NULL keeps missing values visible when you are reading the file yourself. Writing the backslash N token matches what MySQL LOAD DATA INFILE and the Postgres COPY command both read as null, so pick that when the CSV is going straight back into a database. Note that the word option cannot be told apart from a string that genuinely contains the text NULL.

Does it work with Postgres and SQL Server dumps, or only MySQL?

All three, as far as INSERT syntax goes. Backtick, double quote and square bracket identifiers are all stripped, schema prefixes such as public.orders are kept in the table name, and the MySQL INSERT IGNORE, SQLite INSERT OR REPLACE and REPLACE INTO forms are read as ordinary inserts. What differs between dialects is everything around the inserts, and none of that reaches a CSV anyway. A Postgres COPY block is not an INSERT statement, so it is skipped.

My INSERT has no column list. What are the headers?

The statement does not carry names, so the header row reads column_1 through column_n, matching the number of values in the first tuple. A note appears telling you the names were generated. You can either edit the header line in the output panel before copying, or add the column list to the statement in the input panel and let it convert again. If the original CREATE TABLE is in the same file, the names are sitting right there in it.

Some rows show up as patched. What was patched?

A row whose value count did not match the header. Fewer values means the row was padded with empty fields on the right, more values means the extras were cut. Both keep the CSV rectangular, which is what any reader needs. A patched count above zero usually means two INSERT statements against the same table listed different columns, so check the notes panel, which names the table and shows the counts it saw.

Will my accented characters or emoji survive?

Yes in the file, maybe in your spreadsheet. The download is written as UTF-8 with a byte order mark at the front, which is the flag Excel looks for before it decides how to read the bytes. Without that mark Excel on Windows assumes a legacy encoding and turns accented text into garbage. Copying the output text rather than downloading it skips the mark, so use the download button when the data is not plain ASCII.

Can I convert a 400 MB production dump here?

No, and you should not want to. Parsing runs in your browser on the main thread, so a file that size freezes the tab or exhausts memory. Files up to a few megabytes convert instantly. Above that, export straight from the database: mysqldump with the tab option writes tab separated files per table, and Postgres COPY table TO file WITH CSV HEADER writes CSV directly. Both stream from disk and finish in seconds.

Is my SQL sent anywhere?

No. Comment stripping, parsing, CSV formatting and the download all happen in JavaScript inside this page. No request goes out after the page has loaded, nothing is stored between visits, and closing the tab clears both panels. A dump containing real customer data stays on the machine you pasted it on.