CSV to SQL Converter
CSV to SQL Converter
Convert a CSV into runnable SQL INSERT statements for PostgreSQL, MySQL,
SQLite, or SQL Server. An optional CREATE TABLE is generated from type
inference over the whole file. Output is batched into multi-row INSERTs for
fast loads. Generation happens in the browser, so the source data never
leaves the machine.
Before you start
You'll want:
- A CSV with a header row — column names become SQL column names, so avoid reserved words (
order,select,user) unless you're comfortable with the quoting. - The target table name (type it in the toolbar).
- The target dialect (PostgreSQL / MySQL / SQLite / SQL Server). If you don't know, ask your DBA or check the database URL prefix.
- Access to run the generated SQL — a psql/mysql client, DBeaver, the cloud console, whatever you prefer.
How to use it
- Paste/drop your CSV in the left pane.
- Set Table to the fully-qualified target (e.g.
public.userson Postgres,mydb.userson MySQL). - Pick the Dialect. Identifier quoting and boolean types change with it.
- Optional: tick schema to also emit a
CREATE TABLEstatement based on type inference over the whole file. - Tune Batch — the number of rows per
INSERT ... VALUES (...), (...), ...statement. 100 is a safe default; 1000 is faster but hits parameter limits on very wide tables. - Click Generate. Copy or download the
.sqlfile. - Run it against your database. For large files, pipe the file in (
psql -f out.sql) rather than pasting into a GUI.
Options explained
Schema (CREATE TABLE)
When schema is on, the tool scans the whole file and emits a CREATE TABLE
with inferred types. Everything numeric stays numeric, literal true/false
becomes boolean (or TINYINT on MySQL), dates matching YYYY-MM-DD become
DATE, and anything else is TEXT (or NVARCHAR(MAX) on SQL Server).
One non-numeric value in an otherwise-numeric column is enough to downgrade the whole column
to text — this is conservative on purpose.
Batch size
Each INSERT packs batch rows. Bigger batches are faster because the server
parses less SQL, but two hard limits apply:
- SQL Server: max 1000 rows per
VALUESclause. - MySQL's
max_allowed_packet: default 64 MB. Large text columns × large batches will exceed it and the statement will fail.
Start at 100 and raise it if the load is slow.
CSV to PostgreSQL
Selecting PostgreSQL as the dialect produces INSERT INTO "schema"."table" (...)
statements with double-quoted identifiers, NUMERIC for numbers,
BOOLEAN (TRUE/FALSE) for literal true/false, DATE
for YYYY-MM-DD-shaped values, and TEXT for everything else.
Single quotes in string values are escaped the standard way (''), so
O'Brien becomes 'O''Brien'.
When to use this over COPY. For one-off loads, migrations, test
data, or any situation where you don't have file-system access to the server, generated
INSERT statements are the simplest path — paste them into psql, DBeaver, or
the Supabase / Neon / RDS console and you're done.
For serious bulk loads against a self-hosted database where you do have file
access, \copy is 5–20× faster:
\copy my_table FROM 'data.csv' WITH (FORMAT csv, HEADER);
Postgres gotchas: quoted identifiers are case-sensitive
("User_ID" ≠ "user_id"), so avoid uppercase in your CSV headers
unless you really want it. Reserved words like user, order,
group, offset, window must stay quoted everywhere
downstream — rename the column before importing if you can. Empty cells become empty
strings, not NULL; run an UPDATE … SET col = NULL WHERE col = ''
afterwards if you need real nulls.
CSV to MySQL
Selecting MySQL emits statements with backtick-quoted identifiers
(`user_id`), TINYINT(1) for booleans, and TEXT
(or LONGTEXT for long values) as the default string type. Works identically
for MariaDB — the dialects are compatible for this surface.
When to use this over LOAD DATA INFILE. The native fast path
on MySQL is:
LOAD DATA LOCAL INFILE 'data.csv'
INTO TABLE my_table
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES;
That's the right tool for millions of rows — but it requires file-system access on the
client, the LOCAL modifier to be allowed on the server
(local_infile=1), and the user to have the FILE privilege for
the non-LOCAL variant. Managed MySQL services (RDS, PlanetScale, Aurora) commonly disable
these. When that's the case, generated INSERTs from this tool are the
next-best option.
MySQL gotchas: max_allowed_packet (default 64 MB) caps a
single INSERT statement — lower the Batch setting to 50 if you hit
Got a packet bigger than 'max_allowed_packet'. Booleans are really tinyints,
so WHERE active = TRUE and WHERE active = 1 are interchangeable
but not to every MySQL-compat layer. Zero-dates (0000-00-00) and the MySQL
strict-mode differences bite when your CSV has sentinel date values.
CSV to SQLite
Selecting SQLite produces double-quoted identifiers and explicit column
types (INTEGER, REAL, TEXT, NUMERIC)
even though SQLite uses dynamic type affinity under the hood. Literal booleans are written
as 1/0 because SQLite doesn't have a native boolean type.
When to use this over .import. The sqlite3 CLI has a built-in
CSV importer:
.mode csv
.import data.csv my_table
That's faster and handles the file end-to-end, but it requires shell access to a
sqlite3 binary. Use this page when you need the SQL as a text artifact: loading
from an app or a language that doesn't shell out, embedding a seed file in a migration,
or sharing exact SQL with a teammate. Tools like Django or
Rails migrations are a typical fit — paste the output into a
db/seeds.sql or equivalent.
SQLite gotchas: no native DATE type — dates are stored as
text or integers. The tool keeps date-looking values as text, matching the convention most
ORMs use. PRAGMA journal_mode = MEMORY; before a bulk insert, and wrapping
the whole file in a single BEGIN;/COMMIT;, makes large loads
dramatically faster — the tool does not add the transaction for you.
CSV to SQL Server
Selecting SQL Server (MSSQL / Azure SQL / T-SQL) emits square-bracketed
identifiers ([user_id]), NVARCHAR(MAX) for text,
BIT for booleans, and DATE / DATETIME2 for
date-shaped values.
When to use this over BULK INSERT or bcp. Those
are the fast paths:
BULK INSERT dbo.my_table
FROM 'C:\path\data.csv'
WITH (FORMAT = 'CSV', FIRSTROW = 2);
But they need the CSV to be on a path the SQL Server process can read, which makes them
awkward on Azure SQL, shared instances, or any setup where you're not in
sysadmin. For anything under a few million rows — or when you just want a SQL
file you can run from SSMS or Azure Data Studio — the generated INSERTs are
the pragmatic choice.
SQL Server gotchas: hard cap of 1000 rows per
VALUES clause — keep the Batch setting at 1000 or below.
For very large files, add a GO separator between statements when pasting into
SSMS so the parser doesn't try to send the whole script as one batch. Single quotes are
escaped as '', same as Postgres. Identity columns don't auto-increment when
you supply values unless you wrap the load in SET IDENTITY_INSERT dbo.my_table ON;
… OFF;.
Dialect quick-reference
- PostgreSQL: double-quoted identifiers (
"user_id"),NUMERIC,BOOLEAN,TEXT. - MySQL / MariaDB: backtick-quoted identifiers (
`user_id`),TINYINT(1)for booleans,TEXT/LONGTEXT. - SQLite: double-quoted identifiers, explicit types with dynamic affinity (
INTEGER/REAL/TEXT), booleans as 0/1. - SQL Server (T-SQL): bracket-quoted identifiers (
[user_id]),NVARCHAR(MAX),BITfor booleans.
Example
Input:
id,name,active,joined
1,Alice,true,2024-01-03
2,"Bob, Jr.",false,2024-02-14
Output (PostgreSQL, schema on, batch=100):
CREATE TABLE "my_table" (
"id" NUMERIC,
"name" TEXT,
"active" BOOLEAN,
"joined" DATE
);
INSERT INTO "my_table" ("id", "name", "active", "joined") VALUES
(1, 'Alice', TRUE, '2024-01-03'),
(2, 'Bob, Jr.', FALSE, '2024-02-14');
Tips & common pitfalls
- Empty strings vs. NULL. A blank cell becomes the literal empty string
'', notNULL. If you need NULLs for blanks, post-process (UPDATE ... SET col = NULL WHERE col = '') or fix the source. - Column names with spaces or special characters. They're quoted correctly per dialect, but checking them is a common source of "column does not exist" errors. Rename in the CSV header if you can.
- Reserved words.
order,group,table,userall parse fine because of quoting, but they'll bite you in every downstream query that forgets to quote them. Rename. - Scientific notation. Excel exports very large IDs like
1.23E+15. Those get inferred as numeric and lose precision — re-export as text or check the output. - Dry-run first. Run the
CREATE TABLE+ a smallINSERTsubset before pushing the whole file, especially on production. - Wrap in a transaction when loading to production. The tool doesn't add
BEGIN; ... COMMIT;for you; paste your own around the output.
Troubleshooting
"syntax error at or near…" on my database.
99% of the time the wrong dialect is selected. PostgreSQL doesn't accept backticks; MySQL doesn't accept square brackets. Regenerate with the right dialect.
Numeric columns came out as text.
At least one value in that column wasn't purely numeric. Check for stray spaces, the literal string "N/A", or currency symbols. Clean the CSV and regenerate.
MySQL: "Got a packet bigger than 'max_allowed_packet' bytes".
Lower the Batch setting (try 50), or increase max_allowed_packet on the server side.
SQL Server: "The number of row value expressions in the INSERT statement exceeds the maximum…".
Keep Batch at 1000 or below.
Frequently asked questions
Why batch INSERT statements?
Multi-row INSERT ... VALUES (...), (...), (...) is an order of magnitude faster than one statement per row on every major database — the server parses one statement instead of N.
Does this upload my CSV?
No. Everything runs in your browser. The SQL never leaves the page until you copy or download it. See the privacy policy for details.
Can it also generate UPDATE or UPSERT statements?
Not yet. Open to the request if a specific dialect would help — drop me a line.
What if my CSV doesn't have a header row?
Add one before converting. SQL columns need names; inferring names like col_1, col_2 would give output you'd just have to rename anyway.