csvkit.org
CSV (Comma-Separated Values) utilities, in the browser
Say hi →

CSV to SQL Converter

updated 23 April 2026 · jump to PostgreSQL, MySQL, SQLite, or SQL Server specifics

options
Drop a .csv file here, or
ready

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:

How to use it

  1. Paste/drop your CSV in the left pane.
  2. Set Table to the fully-qualified target (e.g. public.users on Postgres, mydb.users on MySQL).
  3. Pick the Dialect. Identifier quoting and boolean types change with it.
  4. Optional: tick schema to also emit a CREATE TABLE statement based on type inference over the whole file.
  5. 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.
  6. Click Generate. Copy or download the .sql file.
  7. 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:

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

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

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.