Skip to main content

CSV to SQL converter

Turn a CSV into CREATE TABLE plus INSERT statements. Column types are inferred from the data, and the quoting follows the dialect you pick.

Parsed and written in this tab. No database is contacted, nothing is executed, and no row is uploaded.

A CSV has no types — every cell is text until something decides otherwise. Turning one into SQL therefore means two jobs: guessing a sensible column type from the values you actually have, and quoting everything correctly for the database you are targeting. This page does both and shows its work. It reads the header row, scans every value in each column, proposes INT, DOUBLE, DATE, BOOLEAN or a sized VARCHAR, and lets you rename any column before it writes a line. Then it emits a CREATE TABLE and as many multi-row INSERT statements as your batch size implies, in MySQL, PostgreSQL or SQLite syntax — which differ in more places than most converters admit.

Key facts about CSV to SQL converter

Key facts about CSV to SQL converter
DialectsMySQL (backtick identifiers), PostgreSQL and SQLite (double-quoted identifiers) — the type names and boolean literals differ too
Type inferenceA column is INT only if every filled value is an integer, DOUBLE if every value is numeric, BOOLEAN on true/false/yes/no/t/f, DATE on YYYY-MM-DD, otherwise VARCHAR sized to the longest value
Leading-zero ruleA value like 01234 is treated as text, not a number — zip codes and phone numbers survive as written instead of becoming 1234
Big integersAnything past 2,147,483,647 promotes the column to BIGINT rather than overflowing an INT
VARCHAR sizingRounded up to 32, 64, 128, 255, 512 or 1000 based on the longest value; past 1000 the column becomes TEXT
SQLite typesSQLite has no DATE or BOOLEAN, so dates become TEXT and booleans INTEGER 0/1 — matching how SQLite actually stores them
Quote escapingA single quote is doubled in every dialect; for MySQL a backslash is doubled as well, because MySQL treats it as an escape inside string literals
Batching1, 50, 100 or 500 rows per INSERT. Multi-row INSERTs are dramatically faster to load than one statement per row
NOT NULLApplied only to columns where no row is blank — the tool never claims a constraint your data would violate
Primary keyOptional auto-increment column: AUTO_INCREMENT on MySQL, SERIAL on PostgreSQL, INTEGER PRIMARY KEY AUTOINCREMENT on SQLite. It is renamed automatically if your data already has an id column
Column namesSpaces and punctuation become underscores, duplicates get a numeric suffix, and a name starting with a digit is prefixed — each one editable before you export
Input ceiling10 MB of CSV text; the whole result is held in memory, so very large exports are better handled by your database's own loader

What happens to your file

The CSV is read by a FileReader in this tab and parsed by Papa Parse; the SQL is built by string concatenation in page memory and downloaded as a Blob. No database is contacted, nothing is executed, and no row is transmitted — this tool writes a text file, it does not connect to anything. That distinction matters for the usual reason people convert a CSV to SQL: the data is often a production export sitting on someone's laptop. It never leaves this tab, there is no server-side step, and nothing is written to localStorage, so a reload clears the parsed rows entirely.

About this tool

  1. 1

    Load the CSV

    Drop the file, browse for it, or paste the text. The first row must name the columns — that is where the table's columns come from.

  2. 2

    Pick the dialect first

    MySQL, PostgreSQL or SQLite. This changes the identifier quoting, the type names, the boolean literals and the transaction keywords, so set it before you read the inferred types.

  3. 3

    Review the inferred types

    The table lists each source column, the name it will get, and the type. Anything you disagree with can be renamed here; the type follows the data, so fix a wrong type by fixing the values.

  4. 4

    Name the table

    The file name is used as a starting point, sanitised. Spaces become underscores and a leading digit gets a prefix so the identifier is valid.

  5. 5

    Choose the statement options

    CREATE TABLE and IF NOT EXISTS are on by default. Add DROP TABLE first for a clean re-import, an auto-increment id when your data has no key, and a transaction wrap so a failed row does not leave a half-loaded table.

  6. 6

    Set the batch size and export

    100 rows per INSERT is a good default for a few thousand rows. Copy the SQL into your client, or download the .sql and run it with mysql, psql or sqlite3.

Specs & compatibility
Input.csv, .tsv or .txt with a header row, or CSV pasted into the box; up to 10 MB
OutputA .sql text file, or the same text copied to the clipboard
ParserPapa Parse 5 in header mode — it sniffs comma, semicolon and tab, and honours quoted fields containing the delimiter
Statement shapeCREATE TABLE [IF NOT EXISTS], optional DROP TABLE IF EXISTS first, then INSERT INTO … VALUES with one tuple per row
Transaction wrappingOptional — START TRANSACTION/COMMIT on MySQL, BEGIN/COMMIT on PostgreSQL and SQLite
PreviewThe first 20,000 characters are shown in the page; the download and the clipboard always carry the whole thing
Browser supportChrome, Edge, Firefox and Safari; the copy button needs a secure context
OfflineWorks with the network off once the page has loaded
  • Set the dialect before anything else — the whole preview is regenerated against it, including the types you are about to review.
  • Wrap in a transaction for anything over a few hundred rows: on failure nothing is committed, and on PostgreSQL it is also markedly faster.
  • If a column you expect to be numeric shows as VARCHAR, one value in it is not a number — a stray footer row and a thousands separator are the two usual causes.
  • Turn off 'Empty value → NULL' when an empty cell genuinely means an empty string rather than missing data; the default assumes missing.
  • A batch of 500 rows can exceed MySQL's max_allowed_packet on a default server. Drop to 100 if the import dies with a packet error.
  • Need the other direction? The SQL to CSV tool extracts rows back out of INSERT statements.
  • MySQL, PostgreSQL and SQLite with dialect-correct identifiers and types
  • Per-column type inference with a leading-zero guard for codes and IDs
  • Editable column names, sanitised to valid identifiers
  • Batched multi-row INSERTs at 1, 50, 100 or 500 rows
  • Optional DROP TABLE, IF NOT EXISTS, auto-increment key and transaction wrap
  • Live SQL preview, clipboard copy and .sql download
  • Seeding a development database from a spreadsheet someone in the business maintains.
  • Turning an exported report into a table you can actually query with joins.
  • Building fixture data for tests without hand-writing a hundred INSERT statements.
  • Moving a small dataset between engines by generating the target dialect directly.
  • Preparing a migration file for a schema change review, where the SQL is the artefact being reviewed.
  • Loading a CSV into SQLite for local analysis when the CSV import in your client refuses the file.
By scanning every filled value in the column, not by sampling the first row. A column becomes an integer type only if every non-empty value is an integer, a floating type if every value is numeric, boolean if every value is one of true/false/yes/no/t/f, a date type if every value matches YYYY-MM-DD, and text otherwise. One stray value is enough to move the whole column to VARCHAR — which is the conservative and correct outcome, because a type that your data violates fails at import time instead.
Because it starts with a zero. A value like 01234 is an identifier, not a quantity: storing it as an integer would silently turn it into 1234 and there is no way to get the zero back. The same rule protects phone numbers, product codes and account numbers with leading zeros. If you genuinely want the numeric value, strip the leading zeros in the spreadsheet before converting.
More than the identifier quoting. MySQL quotes identifiers with backticks, calls a 4-byte integer INT, has no real boolean so it uses TINYINT(1) with 1 and 0, writes DATETIME, starts a transaction with START TRANSACTION, and treats a backslash inside a string literal as an escape character. PostgreSQL uses double quotes, INTEGER and DOUBLE PRECISION, real TRUE and FALSE literals, TIMESTAMP, and BEGIN. SQLite uses double quotes and has only INTEGER, REAL and TEXT, so dates are stored as text and booleans as 0 and 1. Picking the wrong one usually produces SQL that runs but stores the wrong thing.
No, to both. The CSV is parsed in this browser tab and the SQL is assembled as a string in page memory; the download is a local Blob. Nothing connects to a database — this page writes statements, it never executes them, and there is no connection form anywhere in it. You can disconnect from the internet after the page loads and the conversion still works.
One hundred is a sensible default. A multi-row INSERT is far faster than one statement per row because the database parses and commits once for the whole batch, but very large batches can hit server limits — MySQL's max_allowed_packet is the usual wall, and a 500-row batch of wide text columns can exceed it. If an import fails with a packet or statement-size error, halve the batch size and try again. Use 1 row per statement only when you need to see exactly which row failed.
Yes. Papa Parse handles the CSV side — a quoted field containing the delimiter or a line break is read as one value — and the SQL side doubles every single quote so the literal closes where it should. For MySQL, backslashes are doubled as well, because MySQL interprets them inside string literals by default while PostgreSQL and SQLite do not. That difference is exactly the kind of thing that turns a clean-looking export into corrupted rows, so it is handled per dialect rather than once for all three.
View all

Updated

We use anonymous analytics to improve ToolChamp. No personal data is stored or sold. Privacy Policy