CSV to SQL Converter
Generate CREATE TABLE and batched INSERT statements from CSV or JSON, with dialect-correct quoting and escaping — entirely on this device.
How many rows each INSERT carries. SQL Server caps one VALUES list at 1,000 rows — larger batches are clamped there. A cell that is exactly NULL (case-sensitive) is written as SQL NULL instead of the text 'NULL'.
SQL statements appear here as you type.
Generated SQL is data, not advice — review it before running it against anything that matters.
The column mapping appears after the first successful parse.
Press ? for shortcuts
Do it offline:$ sqlite3 app.db ".import --csv data.csv customers"
How to use it
- Paste CSV or JSON, or drop a file — a badge names the detected format; one click corrects it.
- Pick dialect and table name, then check the column grid — override any inferred type per column.
- Set batch size, empty-cell handling and identifier case; DROP TABLE and TRUNCATE need their own confirmation.
- Copy the preview or download the whole script as .sql — the preview stops at 100 statements, the download never does.
Seeding a database from an export usually means hand-writing INSERT statements around every apostrophe. Paste CSV or JSON rows and get CREATE TABLE plus batched INSERTs for PostgreSQL, MySQL, SQLite or SQL Server — column types inferred from every row, identifiers quoted where the dialect demands it, string values escaped the way that dialect actually reads them. Nothing you paste leaves this device.
Examples
id,name,city,rank
1,O'Brien,Cork,3
2,Ana,Lisbon,1
3,Löwe,Berlin,2
4,Sam,Leeds,4CREATE TABLE customers (
id INTEGER,
name TEXT,
city TEXT,
rank INTEGER
);
INSERT INTO customers (id, name, city, rank) VALUES
(1, 'O''Brien', 'Cork', 3),
(2, 'Ana', 'Lisbon', 1);
INSERT INTO customers (id, name, city, rank) VALUES
(3, 'Löwe', 'Berlin', 2),
(4, 'Sam', 'Leeds', 4);Batch size 2 packs four rows into two INSERTs. Both numeric columns inferred INTEGER across every row; O'Brien is doubled to O''Brien, PostgreSQL's one string escape.
name,city,rank,id
O'Brien,Cork,3,1
Ana,Lisbon,1,2
Löwe,Berlin,2,3
Sam,Leeds,4,4CREATE TABLE customers (
name TEXT,
city TEXT,
`rank` INT,
id INT
);
INSERT INTO customers (name, city, `rank`, id) VALUES
('O\'Brien', 'Cork', 3, 1),
('Ana', 'Lisbon', 1, 2),
('Löwe', 'Berlin', 2, 3),
('Sam', 'Leeds', 4, 4);The same customers for MySQL: rank is reserved there, so it travels in backticks (a chip says so), and the apostrophe leaves as \' under MySQL's backslash escaping.
[{"id":1,"name":"Ada","tags":["maps","sql"]},{"id":2,"city":"Oslo"}]CREATE TABLE people (
id INTEGER,
name TEXT,
tags TEXT,
city TEXT
);
INSERT INTO people (id, name, tags, city) VALUES
(1, 'Ada', '["maps","sql"]', NULL),
(2, NULL, NULL, 'Oslo');Columns are the key union in first-seen order; absent values become NULL. The tags array is not relational — it lands as JSON text, flagged by a warning chip.
sku,price,in_stockCREATE TABLE products (
sku TEXT,
price TEXT,
in_stock TEXT
);A lone header still yields the table definition, and a chip notes the zero data rows. With nothing to observe, every column stays TEXT.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Escaping is the dialect's rule, not a generic one
PostgreSQL and SQLite know one escape inside a string — the doubled quote. MySQL also reads backslash escapes, so this tool writes \' and doubles every backslash: C:\tmp survives, where quote-doubling alone would hand MySQL a tab.
Type names and booleans translate per target
One floating column is DOUBLE PRECISION in PostgreSQL, DOUBLE in MySQL, REAL in SQLite and FLOAT in SQL Server; true is TRUE for the first two, 1 for the rest. SQL Server strings are NVARCHAR(MAX), non-ASCII values prefixed N'…'.
SQLite spells TRUNCATE as DELETE FROM
SQLite has no TRUNCATE statement — its documentation names an unqualified DELETE FROM as the fast equivalent, so that is what the truncate toggle emits there.
SQL Server caps one INSERT at 1,000 rows
The T-SQL table value constructor refuses more, so bigger batch sizes clamp to 1,000 with a chip; the other dialects take yours as given.
Digits that numbers would damage stay TEXT
Integers beyond 2^53 round inside a double, and 00501 stops being a ZIP code as 501 — both stay quoted TEXT (the former with a warning) unless overridden. Fixed-point becomes DECIMAL(p,s), emitted unquoted.
JSON numbers arrive pre-rounded
JSON goes through the JavaScript parser, so a bare number above 2^53 lost digits before conversion began. Send big identifiers as JSON strings — or as CSV, where digit strings arrive intact.
Empty, '' and the word NULL differ
An empty cell becomes SQL NULL. A setting writes '' instead, text columns only — elsewhere '' is no literal, so NULL still appears and a chip lists where. The exact cell NULL, uppercase, converts only when its toggle is on.
Semicolons and line breaks ride inside literals
a;b or a quoted multi-line address emits inside one string literal, where neither character ends the statement. Statement counts here are computed quote-aware, never by splitting on semicolons.
snake_case renames columns, never your table
The case option lowers First Name into first_name; collisions get numeric suffixes. The table name is used whole, as one identifier — no schema splitting, no re-casing.
The preview stops at 100 statements
Totals stay exact at any size; the .sql download assembles each batch in a worker. Past 100 MB — 25 MB for JSON — the tool refuses, naming the limit, never truncating.
Common errors
Input is larger than the 100 MB limit for this tool.- Refusal, not truncation — a script silently missing rows would look finished. At that size, the sqlite3 line below imports the file directly.
Invalid JSON: Expected property name or '}' at line 1, column 14 — fix the syntax there, or set the format to CSV if this is delimited data.- {id: 2} is a JavaScript object literal, not JSON — keys need double quotes. The position marks the first offending key.
JSON array element 1 is a number — every row must be an object like {"column": value}, so the rows share named columns.- Bare values carry no column names, and INSERT tuples need them. Wrap each value in an object, or paste CSV with a header row.
DROP TABLE is enabled but not confirmed — tick the destructive confirmation to include it, or turn the toggle off.- Destructive statements are never included silently. The confirmation checkbox beside those toggles is what this message asks for.
Quoted field opened at line 2, column 3 never closes — this usually breaks every following row; close the quote, or write a literal quote inside a quoted field as "".- One unclosed quote makes every later row read as the same cell — add the closing quote where the position points, or double literal quotes inside the field.
FAQ
Is the generated SQL safe to run against a production database?
Review it first. Escaping follows each vendor's manual, but these are plain statements, not parameterized queries — and only you know what the target table already holds.
What actually differs between the four dialects?
Identifier quoting (double quotes, backticks or brackets), type names such as DOUBLE PRECISION versus REAL, boolean literals TRUE/FALSE versus 1/0, the string escape rule, and SQL Server's 1,000-row INSERT ceiling.
Does my data leave the browser while it converts?
No. Parsing, inference and generation run on this device — bigger inputs in a background worker — and dropped files are read locally. There is no upload endpoint for rows to reach.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Convert again now — above 2 MB this is the explicit Run.
- ⌘/Ctrl + S
- Download the full script as .sql.
- ⌘/Ctrl + C
- Copy the script when nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy even while a selection exists.
- t
- Cycle the theme.
- ? or ⌘/Ctrl + /
- Show the shortcut help overlay.
- Esc
- Close the topmost overlay.