How to convert an excel spreadsheet to sql insert statements
- Step 1Open the SQL generator — Go to the Excel → SQL tool. The Excel entry point redirects to the JSON to SQL converter — that is the actual engine and it accepts
.xlsx,.xls,.ods,.csv, and.jsondirectly, so there is no separate step to convert your file first. - Step 2Drop in your workbook — Drag the file onto the dropzone. SheetJS reads the first sheet only and uses row 1 as column headers. If your data lives on a second tab, move or copy it to the first sheet before exporting, because the tool will not see it.
- Step 3Set the table name — Type the target table into the Table name field (default
data). This becomes the identifier in bothCREATE TABLEandINSERT INTO, quoted correctly for the dialect you pick. - Step 4Pick the dialect — Choose PostgreSQL, MySQL, or SQLite. The choice changes identifier quoting (backticks vs double quotes), the boolean representation (
1/0vsTRUE/FALSE), and the inferred numeric type (DOUBLE PRECISIONvsREAL). - Step 5Toggle CREATE TABLE / IF NOT EXISTS / ON CONFLICT — Enable Include CREATE TABLE to prepend DDL with inferred types; keep IF NOT EXISTS on for re-runnable scripts; tick ON CONFLICT DO NOTHING to make inserts idempotent (
INSERT IGNOREon MySQL). Use Exclude columns (comma-separated) to drop audit fields likeinternal_id, _meta. - Step 6Generate, copy, or download — Click Generate SQL. The output panel shows rows / columns / statement count and a preview (truncated past 5,000 chars on screen). Use Copy for psql/Workbench, or Download SQL to save a
.sqlfile for a migration folder.
The real option set
Every control that actually exists on the SQL generator, with its default and effect. There is no batch-size field, no DATE type picker, and no drag-to-reorder — those are not in the tool.
| Control | Default | What it does |
|---|---|---|
| Table name | data | Identifier used in CREATE TABLE and INSERT INTO, quoted per dialect |
| SQL dialect | PostgreSQL | Three buttons: PostgreSQL, MySQL, SQLite — changes quoting, booleans, numeric type |
| Exclude columns | (none) | Comma-separated header names to omit from columns, e.g. internal_id, _meta |
| Include CREATE TABLE | On | Prepends a CREATE TABLE with types inferred from the data |
| IF NOT EXISTS | On | Adds IF NOT EXISTS to the CREATE TABLE so re-runs don't error |
| ON CONFLICT DO NOTHING | Off | ON CONFLICT DO NOTHING (Postgres/SQLite) or INSERT IGNORE INTO (MySQL) |
How a column's SQL type is inferred
The CREATE TABLE type for each column is decided by the JavaScript values SheetJS produces. There is no DATE, TIMESTAMP, VARCHAR, NUMERIC, or JSON type — every non-numeric, non-boolean value falls through to TEXT.
| Cell values in the column | Postgres / SQLite type | MySQL type |
|---|---|---|
| All booleans (TRUE/FALSE cells) | BOOLEAN | TINYINT(1) |
| All whole numbers | INTEGER | INTEGER |
| Numbers incl. decimals | DOUBLE PRECISION | REAL |
| Text, mixed, or anything else | TEXT | TEXT |
| Entirely empty / all NULL | TEXT | TEXT |
| Excel date cell (serial number) | INTEGER (serial, not a date) | INTEGER (serial, not a date) |
Cookbook
Real before/after fragments. Inputs are shown as the spreadsheet rows; outputs are the generated SQL with default options (Postgres, CREATE TABLE on, IF NOT EXISTS on).
Basic seed: a small lookup table
A two-column category sheet becomes a CREATE TABLE plus a single multi-row INSERT. Whole numbers infer INTEGER, the label column infers TEXT.
Excel (Sheet1):
id | name
1 | Books
2 | Toys
Generated SQL (Postgres, table name 'category'):
CREATE TABLE IF NOT EXISTS "category" (
"id" INTEGER,
"name" TEXT
);
INSERT INTO "category" ("id", "name")
VALUES
(1, 'Books'),
(2, 'Toys');Apostrophes survive — quotes are doubled
The classic INSERT-breaker. A surname with an apostrophe is escaped by doubling the single quote, which is valid in Postgres, MySQL, and SQLite alike.
Excel:
id | surname
1 | O'Brien
2 | D'Angelo
Generated SQL:
INSERT INTO "data" ("id", "surname")
VALUES
(1, 'O''Brien'),
(2, 'D''Angelo');Blank cells become NULL, not empty strings
An empty middle-name cell is written as NULL so a nullable column stays null and COALESCE defaults fire correctly. SheetJS is read with defval: null, so the gap is preserved.
Excel:
id | first | middle
1 | Ada | Lin
2 | Bo |
Generated SQL:
INSERT INTO "data" ("id", "first", "middle")
VALUES
(1, 'Ada', 'Lin'),
(2, 'Bo', NULL);Fix Excel date columns before generating
Excel stores dates as serial numbers, and SheetJS hands them over as numbers — so a date column infers INTEGER and you get 45292 instead of '2024-01-01'. Run the sheet through the date standardizer first so the column is real text in ISO form, then it imports as TEXT you can CAST.
Excel date column 'joined' shows: 2024-01-01
Raw generated value (no fix): 45292 -- INTEGER serial, wrong
After excel-date-standardizer (ISO text):
INSERT INTO "data" ("id", "joined")
VALUES
(1, '2024-01-01');
-- then in SQL: ALTER ... USING joined::date (Postgres)Idempotent re-runs with ON CONFLICT
For a seed file you run repeatedly against a table with a primary key, tick ON CONFLICT DO NOTHING so existing rows are skipped instead of raising a duplicate-key error.
Options: dialect Postgres, ON CONFLICT DO NOTHING
INSERT INTO "category" ("id", "name")
VALUES
(1, 'Books'),
(2, 'Toys') ON CONFLICT DO NOTHING;
MySQL equivalent (same toggle):
INSERT IGNORE INTO `category` (`id`, `name`)
VALUES
(1, 'Books'),
(2, 'Toys');Edge cases and what actually happens
Workbook has multiple sheets
First sheet onlySheetJS reads wb.SheetNames[0] — only the first tab is converted. Data on other sheets is silently ignored. Move the target data to the first sheet, or split the workbook, before generating. There is no sheet picker in this tool.
A date column came out as 45292
By designExcel dates are serial numbers and the workbook is parsed without cellDates, so dates arrive as numbers and infer INTEGER. This is expected, not a bug. Standardise the column to ISO text first with the date standardizer, then CAST/ALTER to a date type in SQL.
Header row missing — data starts on row 1
Wrong column namesRow 1 is always treated as headers. If your first data row is mistaken for headers, every column is named after that row's values and you lose one record. Add a proper header row in Excel before generating.
Large integer IDs lose precision
Precision riskValues beyond 2^53 (about 9 quadrillion) exceed JavaScript's safe integer range during parsing and can drift. 18-digit Snowflake-style IDs are at risk. Format such columns as Text in Excel so they pass through as TEXT strings exactly.
A cell contains a literal NULL or numeric-looking string
TEXT preservedIf a column mixes numbers and text (e.g. N/A among integers), the whole column infers TEXT and the numbers are quoted as strings. That is correct for safety — split or clean the column first if you need a numeric type.
Free tier hits the 100-row code-gen cap
Upgrade requiredCode generation is quota-limited: Free allows 1 run/day and a 100-row ceiling; Pro 5/day at 1,000 rows; Pro+Media 50/day at 10,000 rows; Developer unlimited. Beyond your tier's row cap the output is truncated. This is separate from the Excel file-size ceiling.
Booleans show as 1/0 in MySQL but TRUE/FALSE in Postgres
ExpectedBoolean cells render as TRUE/FALSE for Postgres/SQLite and 1/0 for MySQL (column typed TINYINT(1)). Pick the dialect that matches your target so the values land correctly.
Special characters in column names
Quoted, supportedHeaders like Order Date or reserved words like select are wrapped in double quotes (Postgres/SQLite) or backticks (MySQL), so they remain valid identifiers. They are not auto-renamed; use the header sanitizer first if you want snake_case.
Empty workbook or only a header row
No statementsWith zero data rows the engine emits a comment such as -- Empty array, no statements generated. Confirm your data actually starts on row 2 of the first sheet.
Frequently asked questions
Which SQL dialects does it support?
Three: PostgreSQL (default), MySQL, and SQLite. There is no Oracle, SQL Server, or generic ANSI mode. The dialect button changes identifier quoting, the boolean representation, and the inferred floating-point type.
How are apostrophes and quotes escaped?
Single quotes inside values are doubled (''), which is the standard string-escape in all three supported dialects. So O'Brien becomes 'O''Brien'. The tool does not use Postgres dollar-quoting ($$) — it uses doubling everywhere.
Does it generate CREATE TABLE?
Yes, when Include CREATE TABLE is on (the default). Column types are inferred from the data: BOOLEAN/TINYINT(1), INTEGER, DOUBLE PRECISION/REAL, or TEXT. There is no DATE, VARCHAR(n), or NUMERIC(p,s) — those would need a manual edit afterward.
Can I control the rows-per-INSERT batch size?
Not from the UI. Rows are grouped into multi-row VALUES statements of 100 each automatically. There is no batch-size field on the tool, despite what older descriptions implied.
Are blank cells inserted as NULL or empty string?
As NULL. The workbook is parsed with defval: null, so an empty cell becomes a genuine SQL NULL, not ''. This keeps nullable columns and COALESCE defaults working.
Why did my date column come out as a number like 45292?
Excel stores dates as serial numbers and the parser does not coerce them to date objects, so they arrive as integers and infer INTEGER. Fix it by standardising the column to ISO text with the date standardizer before generating, then CAST to date in SQL.
Does it read more than one sheet?
No — only the first sheet of the workbook. Other tabs are ignored. Move your data to the first sheet, or use the sheet joiner to combine tabs into one before exporting.
Can I exclude certain columns?
Yes. The Exclude columns field takes a comma-separated list of header names (e.g. internal_id, _meta) and those columns are dropped from both the CREATE TABLE and the INSERTs.
Is the file uploaded to a server?
No. The workbook is read in your browser with SheetJS and converted locally. Nothing is sent to a server — only an anonymous run counter for dashboard stats, which you can opt out of. This is why it is safe for seed data with real PII.
What is the row limit?
Two limits apply. The Excel family caps file size at 5 MB (Free), 50 MB (Pro), 200 MB (Pro+Media), 500 MB (Developer). Separately, code generation is quota-limited to 100 rows (Free), 1,000 (Pro), 10,000 (Pro+Media), unlimited (Developer). The smaller of the two governs your output.
How do I make the script re-runnable?
Keep IF NOT EXISTS on (so the CREATE TABLE won't error on the second run) and tick ON CONFLICT DO NOTHING so existing rows are skipped rather than raising a duplicate-key error.
What other Excel dev-bridge tools pair with this?
For other code targets, see Excel → Python and the Tailwind HTML table export. To clean data first, use formula-to-value and the date standardizer.
Privacy first
Every JAD Excel tool runs entirely in your browser using SheetJS and ExcelJS. Your spreadsheets, formulas, and data never leave your device — verified by zero outbound network requests during processing.