Convert CSV to SQL
Builds CREATE TABLE and INSERT statements from a spreadsheet export — and shows you every column type before it does.
- SECURE
- NO UPLOADS
- NO SIGNUP
- BROWSER BASED
- FREE
- FOREVER.
Support us with a link or a share
- Rows
- —
- Statements
- —
Paste your CSV above, or drop a .csv file. Nothing is uploaded — the statements are built in this tab.
1 writes one INSERT per row.
The one thing a CSV to SQL converter has to get right
A spreadsheet export records no types. Every value in the file is a run of characters,
and something has to decide whether 01234 is a number or a postcode before
a single CREATE TABLE can be written. Get that wrong and the damage is
permanent and silent: the column is declared INTEGER, the database happily
stores 1234, and nobody notices until a parcel goes to the wrong town.
So the guess here is deliberately timid and, more importantly, it is shown to you
before you copy anything. A column becomes a number only when every non-empty value in
it is one. A leading zero disqualifies a value outright — 007 is an agent
number, not seven. Forms that mean different things to different engines are treated as
text on principle: 0x10, 1e5, +5 and
Infinity all stay quoted rather than becoming whatever the target database
happens to think they are. And every column you look at can be overridden on its own,
because the mistake is nearly always about one column and not the whole file.
If the source has no primary-key column yet, add identifiers before treating the generated statements as a repeatable seed. A UUID generator can produce one v4 or v7 value per row as plain lines; keep that new column as text here so every hyphen and hexadecimal digit reaches the database unchanged.
The same guess has to be made from the other direction when the source is an API response rather than a spreadsheet, except that there it starts from firmer ground: JSON to SQL is handed real types by the format itself and only has to decide how deep to flatten the nesting.
The return leg has no guessing in it at all, which is worth knowing if the data is
making a round trip. A dump declares what every column is, so
SQL to CSV reads the types out of the
CREATE TABLE rather than inferring them from the values. Type inference is
the price of coming from a spreadsheet, and it is only ever paid in this direction.
CSV and SQL, in brief
| Full name | Comma-Separated Values |
|---|---|
| Extension |
.csv
|
| Format type | Plain text table, one record per line |
| MIME type |
text/csv
|
| Full name | Structured Query Language |
|---|---|
| Extension |
.sql
|
| Format type | Plain text statements, not a data format |
| MIME type |
application/sql
|
Quoting everything is not paranoia
Every identifier is written with the dialect's quote character, every time, whether it
needs it or not. There is no cleverness deciding which names are safe, because that
decision has a long list of exceptions and getting it wrong produces a file that fails
halfway through. A column called order is a reserved word. One called
first name has a space in it. One that genuinely contains a backtick will
break a MySQL statement in a way that looks like a syntax error somewhere else entirely.
Quoting unconditionally makes all three uninteresting.
Apostrophes inside values are doubled rather than backslash-escaped. Both look correct
in MySQL; only doubling is correct in PostgreSQL. Since a generated file has a habit of
being run somewhere other than where it was made, the version that works everywhere is
the only one offered. A backslash that is part of the source data is a separate case:
MySQL treats it as an escape under its default SQL mode, so the MySQL dialect doubles it
and writes line breaks and tabs with MySQL's explicit forms. The other three dialects
leave those characters alone. SQL Server text gets its required N prefix so
Unicode reaches the NVARCHAR column as Unicode. Those details keep the stored
value unchanged rather than merely making the statement look quoted. Need one value
rather than a whole table? The SQL escape tool writes a single
string literal for MySQL, SQL Server, SQLite or PostgreSQL, and reads one back.
Rows per statement is a real choice, not a preference
One INSERT per row reads beautifully in a diff and is what you want in a
migration somebody has to review. A hundred rows in a single multi-row
VALUES clause runs dramatically faster, because the cost is per statement
far more than per row — on a large import the difference is minutes against hours. The
control is a number rather than a toggle, so you can put it wherever your case sits.
Speed has a cost in legibility, though. A hundred rows packed into one
VALUES clause arrives as a single enormous line, and reading what you are
about to run against a live database becomes guesswork. Passing it through the
SQL formatter puts the statement back on separate
lines first, which is worth the extra step on anything irreversible.
Rows that are shorter than the header get padded rather than truncated. Writing fewer values would produce a column-count error, and that error surfaces in your database rather than here, which is the worst possible place for it to appear.
When you should not use this at all
If the file and the database are in the same place, import the file. LOAD DATA
INFILE, \copy, .import and every GUI client's import
wizard are faster, handle far larger files, and skip this step completely. Saying so
costs nothing and is the honest answer to why this whole category of tool is smaller
than it looks.
Statements earn their place when the data has to travel as code. A seed file checked into a repository. A migration that gets read by a colleague before it runs. A row set destined for an environment where you cannot put a file on the disk at all. In those cases you do not want an import — you want text you can read, diff and version.
Your export never leaves the tab
The parser and the statement builder are JavaScript running in this page. There is no endpoint to upload to, which you can confirm the way you would check anyone's claim: open the network panel and drop the file. The reason it matters is what these files tend to be. A CSV on its way into a database is a customer list, an order history or a contact form export — names, addresses, email addresses, sometimes rather more. Handing that to a server-side converter is not a small favor to ask.
Nothing is refused for being large, either. Work that grows with the file runs off the main thread, so the page stays usable while a long export is being read.
Text you can read, diff and version is also the argument for the
CSV to XML converter, and the two pages hit the same wall from
opposite sides. SQL and XML both refuse a column called First Name, and both
have to do something about it — SQL by quoting the identifier so the space survives, XML by
rewriting the name, because an element name has no quoting escape hatch at all. Which of
those two answers a format gives you is most of what separates them here.
Questions
How does it decide the column types?
Conservatively, and it shows you the answer before you copy anything. A column only becomes INTEGER if every non-empty value is a whole number within the signed 64-bit range. A leading zero is never treated as numeric — 01234 is a postcode and 007 is an agent number — and a larger integer stays text so every digit survives. Anything uncertain becomes text, and you can change one column without touching the others.
Which SQL dialect does it write?
Whichever you pick: standard SQL, MySQL, PostgreSQL or SQL Server. The difference is real rather than cosmetic. Identifiers use double quotes, backticks or square brackets, and type names differ too — SQL Server uses BIT and NVARCHAR(MAX), with N-prefixed text literals so Unicode survives. Inferred whole numbers use BIGINT so the stated 64-bit range is real. Every identifier is quoted, so order or first name works without you having to notice.
What happens to apostrophes in my data?
Apostrophes are doubled in all four dialects. MySQL also treats source backslashes and control characters as escapes under its default SQL mode, so its output uses explicit MySQL forms for paths, line breaks and tabs. Standard SQL, PostgreSQL and SQL Server leave those characters alone. That distinction keeps the stored text unchanged after the script runs.
Why would I generate INSERT statements instead of importing the CSV directly?
Often you should import it directly. LOAD DATA INFILE, \copy and your client's import wizard are faster and handle far bigger files. Statements are the right answer when the data has to travel as code: a seed file in a repository, a migration that gets reviewed before it runs, or a row set you need to run against an environment where the file cannot be placed.
Is my file uploaded anywhere?
No. The parsing and the statement building are JavaScript running in this tab, and there is no upload endpoint to send anything to. That matters here more than it looks: a CSV headed for a database is usually a customer list, an order export or a contact form dump, and pasting one into a server-side converter hands a stranger a copy of it.