Skip to content
CornerTools

CSV to SQL Converter

Turn CSV into SQL INSERT statements, with a CREATE TABLE inferred from the data, for MySQL, PostgreSQL, SQL Server or SQLite.

1
1
Rows: 0Characters: 0
Rows per INSERT

Paste or open a CSV file to get the SQL that loads it: a CREATE TABLE whose column types are inferred from every value, and INSERT statements written for MySQL, PostgreSQL, SQL Server or SQLite. The inferred columns can be renamed, retyped and given a primary key, and a duplicate key can be skipped or updated.

Values are written from the text of the file, never through a floating-point number, so long ids and prices keep every digit. It runs in your browser: nothing you paste or open is sent anywhere.

Options

  • Dialect — sets the types, the quoting and the statements. MySQL is the default.
  • Table name — taken from the file's name when you open one, else my_table. A dotted name such as sales.orders is quoted part by part.
  • CSV delimiter — found in the data by default; the CSV may hold quoted values with commas, quotes and line breaks (RFC 4180).
  • Rows per INSERT — 1, 100 (the default), 500 or 1,000 rows in one statement. SQL Server accepts at most 1,000 rows in a VALUES list. For SQLite, a statement that would pass its default limit of 1,000,000 bytes is split, and a note says so.
  • CREATE TABLE, DROP TABLE IF EXISTS first and Wrap in a transaction add those statements. DROP TABLE IF EXISTS needs SQL Server 2016 or later.
  • Empty cells as NULL — on by default. Off, an empty cell in a text column is written ''; number, boolean and date columns cannot hold an empty string, so their empty cells stay NULL, with a note.
  • Read the text NULL as NULL — off by default, so a cell reading NULL is the four-letter string. On, it is SQL NULL in any case.
  • Edit columns — under the output, each column's SQL name, type, Nullable and Primary key. The types offered are those every value fits, plus Text.
  • On a duplicate key — shown once a primary key is set, for every dialect but SQL Server: fail (plain INSERT), skip the row, or update it. The statements for each dialect are listed under How it works.

How it works

Each column's type is the narrowest every value fits. Whole numbers are an integer within 32 bits, a big integer within 64 bits, and text beyond that. Numbers with a fraction are a decimal sized from the data: the scale is the most digits after the point, and the precision the most digits before it plus the scale; past 38 digits the column is text. A value with a leading zero such as 007 makes the column text, so codes and phone numbers keep their zeros. Booleans are true and false, in any case. Dates and date-times are recognised only in ISO form (2026-01-05, 2026-01-05 10:30, with or without an offset), and only when the date exists; other date formats stay text. Empty cells never decide a type; they make the column nullable.

Text columns are sized for the longest value, rounded up to 16, 32, 64, 128, 255, 500 or 1,000 characters. MySQL counts characters; SQL Server's NVARCHAR counts UTF-16 units, so an emoji counts twice there. MySQL's BOOLEAN is an alias for TINYINT(1). SQLite has no fixed column types: its affinities are used, and dates are stored as ISO text.

Strings double each ' in every dialect. MySQL also reads a backslash as an escape under its default sql_mode, so backslashes are doubled there. SQL Server strings are written N'…' so non-Latin text and emoji survive. Identifiers are always quoted: backticks for MySQL, square brackets for SQL Server, double quotes for PostgreSQL and SQLite. Booleans are TRUE and FALSE in MySQL and PostgreSQL, 1 and 0 in SQL Server and SQLite. SQL Server's DATETIMEOFFSET reads an offset as +hh:mm, so +0530 is written +05:30 there and Z as +00:00. Its DATETIME2 and DATETIMEOFFSET hold seven digits of a fractional second, so a longer fraction is cut to seven there, not rounded, and a note names the columns.

  • MySQL — skip: INSERT IGNORE; update: ON DUPLICATE KEY UPDATE col = VALUES(col). MySQL 8.0.20 deprecates VALUES() here in favour of a row alias, which MariaDB does not support; VALUES() still works on MySQL 5.7, 8 and MariaDB.
  • PostgreSQL — ON CONFLICT (key) DO NOTHING or DO UPDATE SET col = EXCLUDED.col. One statement cannot update the same key twice, so repeated keys in the data get a warning naming the rows.
  • SQLite — INSERT OR IGNORE, or ON CONFLICT (key) DO UPDATE (SQLite 3.24 or later).
  • SQL Server — needs a MERGE statement, which the tool does not write; the option is not shown for it.

Examples

A product list into MySQL

Input CSV
sku,name,price,in_stock
007,Masala chai,4.50,true
012,Mug "large",12.00,false
031,Kettle,,true
Options
the defaults
SQL output
CREATE TABLE `my_table` (
  `sku` VARCHAR(16) NOT NULL,
  `name` VARCHAR(16) NOT NULL,
  `price` DECIMAL(4,2),
  `in_stock` BOOLEAN NOT NULL
);

INSERT INTO `my_table` (`sku`, `name`, `price`, `in_stock`) VALUES
  ('007', 'Masala chai', 4.50, TRUE),
  ('012', 'Mug "large"', 12.00, FALSE),
  ('031', 'Kettle', NULL, TRUE);

sku is text, because of its leading zeros. The empty price is NULL, which makes the price column nullable, and the quotes in Mug "large" need no escaping inside a SQL string.

The same list into PostgreSQL, updating existing rows

Input CSV
sku,name,price,in_stock
007,Masala chai,4.50,true
012,Mug "large",12.00,false
031,Kettle,,true
Options
Dialect PostgreSQL, CREATE TABLE off, sku as the primary key, On a duplicate key: Update
SQL output
INSERT INTO "my_table" ("sku", "name", "price", "in_stock") VALUES
  ('007', 'Masala chai', 4.50, TRUE),
  ('012', 'Mug "large"', 12.00, FALSE),
  ('031', 'Kettle', NULL, TRUE)
ON CONFLICT ("sku") DO UPDATE SET "name" = EXCLUDED."name", "price" = EXCLUDED."price", "in_stock" = EXCLUDED."in_stock";

Rows whose sku already exists take the new name, price and stock; the others are inserted.

Into SQL Server

Input CSV
sku,name,price,in_stock
007,Masala chai,4.50,true
012,Mug "large",12.00,false
031,Kettle,,true
Options
Dialect SQL Server, Table name dbo.products
SQL output
CREATE TABLE [dbo].[products] (
  [sku] NVARCHAR(16) NOT NULL,
  [name] NVARCHAR(16) NOT NULL,
  [price] DECIMAL(4,2),
  [in_stock] BIT NOT NULL
);

INSERT INTO [dbo].[products] ([sku], [name], [price], [in_stock]) VALUES
  (N'007', N'Masala chai', 4.50, 1),
  (N'012', N'Mug "large"', 12.00, 0),
  (N'031', N'Kettle', NULL, 1);

Strings are N'…', booleans are BIT values, and the schema and table are bracketed separately.

Into SQLite, skipping rows already there

Input CSV
sku,name,price,in_stock
007,Masala chai,4.50,true
012,Mug "large",12.00,false
031,Kettle,,true
Options
Dialect SQLite, CREATE TABLE off, sku as the primary key, On a duplicate key: Skip
SQL output
INSERT OR IGNORE INTO "my_table" ("sku", "name", "price", "in_stock") VALUES
  ('007', 'Masala chai', 4.50, 1),
  ('012', 'Mug "large"', 12.00, 0),
  ('031', 'Kettle', NULL, 1);

Column types by dialect

The type each kind of value is given
ValuesExampleMySQLPostgreSQLSQL ServerSQLite
Integer42INTINTEGERINTINTEGER
Big integer3000000000BIGINTBIGINTBIGINTINTEGER
Decimal4.50DECIMAL(3,2)NUMERIC(3,2)DECIMAL(3,2)NUMERIC
BooleantrueBOOLEANBOOLEANBITINTEGER
Date2026-01-05DATEDATEDATETEXT
Date-time2026-01-05 10:30DATETIMETIMESTAMPDATETIME2TEXT
Date-time with offset2026-01-05T10:30+05:30VARCHAR(32)TIMESTAMPTZDATETIMEOFFSETTEXT
Text, up to 1,000 charactersMasala chaiVARCHAR(16)TEXTNVARCHAR(16)TEXT
Text, longer1,200 charactersTEXTTEXTNVARCHAR(MAX)TEXT

Sources

The CSV is read as RFC 4180 describes it. The quoting, types and statements follow each database's reference manual, at its default settings.

Sources

Reviewed

Frequently asked questions

At least one value has a leading zero, is past the 64-bit integer range, or is not a number at all. A number column would lose the zeros or reject the value, so the column stays text. Under Edit columns, the types every value fits are offered.
Yes, within your browser's memory: the conversion runs in the background, and an output too long to show is still copied and downloaded whole. For very large loads, a database's own bulk loader (LOAD DATA, COPY, BULK INSERT, .import) is faster than INSERT statements.

See also