Excel to SQL Converter
Load a spreadsheet into a database properly. Column types come from reading whole columns, blanks become NULL, and the identifier quoting matches the dialect you picked.
To convert Excel to SQL, drop your .xlsx above and choose a dialect. You get a CREATE TABLE whose types are inferred from every value in each column, then INSERT statements batched at a size you set. Blank cells become NULL rather than empty strings, identifiers are quoted the way your dialect spells them, and no column is sized from a sample.
Want to clean the sheet before you load it? Open the app
The spreadsheet is the source and the database is the destination
Reference data almost always starts in a spreadsheet, because that is where the people who know it work. Tax rates, shipping zones, product catalogues, store locations, plan definitions. At some point an application needs it, and somebody has to get it into a table.
The import wizards can do it, and they are slow, and they run on the server rather than on your laptop, and the one you want is behind an admin role you do not have. A script you can read and run in your own client is faster and reviewable, which matters when the load is going into production.
The part a converter has to get right is the CREATE TABLE. Everything as TEXT loads and then makes every subsequent query a cast.
Worked example, in MySQL
A sheet of store locations, with the hazards a real one has:
store_code store_name opened_on staff region_code manager
0041 Harbour Road 2019-03-04 12 01 Ada Lovelace
0058 Lakeside Retail 2020-07-19 7 02
0063 High Plains 2021-11-30 4 03 Grace Hopper
And the script:
CREATE TABLE `stores` (
`store_code` TEXT,
`store_name` TEXT,
`opened_on` TEXT,
`staff` DECIMAL(20,0),
`region_code` TEXT,
`manager` TEXT
);
INSERT INTO `stores` (`store_code`, `store_name`, `opened_on`, `staff`, `region_code`, `manager`) VALUES
('0041', 'Harbour Road', '2019-03-04', 12, '01', 'Ada Lovelace'),
('0058', 'Lakeside Retail', '2020-07-19', 7, '02', NULL),
('0063', 'High Plains', '2021-11-30', 4, '03', 'Grace Hopper');
The blank manager cell is NULL, not ''. store_code and region_code are text because of their padding, which is what keeps the codes joinable against your other tables. And staff is DECIMAL(20,0) rather than a bare NUMERIC, for a reason worth its own paragraph.
The dialect differences that actually break scripts
A bare NUMERIC in MySQL means DECIMAL(10,0). That silently refuses an eleven-digit value, so a script generated for Postgres and run against MySQL fails partway through a load on the one row with a long id. MySQL therefore gets DECIMAL(20,0) written out in full.
The identifier quote is different in all four: double quotes in Postgres and SQLite, backticks in MySQL, square brackets in SQL Server. A column called order is a reserved word and needs quoting in every one of them, with the right character.
Booleans differ too: TRUE in Postgres, 1 in MySQL and SQL Server, and a BIT column in the latter. And SQL Server refuses a multi-row VALUES clause with more than a thousand rows, so choosing it clamps the batch size and tells you it did.
Types read from the whole column
- Numeric only when every value in the column round trips exactly. One padded code makes the column text, which is the outcome you want, because a store code that became 41 no longer joins.
- Integer versus decimal is decided from the values: a column with no fractional part anywhere gets the exact type, so counts stay exact and money keeps its precision.
- Boolean only when every value is a real Excel TRUE or FALSE. A column of Y and N stays text, because turning Y into TRUE discards the difference between Y, y and yes.
- Dates stay TEXT in the CREATE TABLE. Excel dates arrive as ISO strings and would load into a DATE column fine, but a converter cannot be certain of that for every row, and one bad value fails the whole statement. Changing TEXT to DATE is a one-word edit if you know.
- No VARCHAR(n) sized from your rows. A competing converter emits
name VARCHAR(27)measured from a six-row sample, which runs perfectly today and truncates the moment the real data arrives.
Practical notes
The table name defaults to the sheet name, cleaned into something SQL will accept without quoting gymnastics. That is almost always what you want and it saves the rename.
The CREATE TABLE can be switched off when the table already exists, which is the usual case for a refresh rather than a first load. Rows are batched five hundred at a time by default: one statement per row is an order of magnitude slower on a real load, and one statement for everything exceeds what most clients will send.
Nothing is added to the script. A competing converter stamps a -- Generated by comment into every SQL file it writes, with no way to turn it off, which then lands in your migrations directory forever.
Frequently Asked Questions
Which dialects are supported?
Postgres, MySQL, SQLite and SQL Server. The choice changes the identifier quote character, the boolean literal, the text type and the exact-number type. Getting any of those wrong fails the script on its first statement, which is why one generic output is not enough.
Why is the MySQL integer column DECIMAL(20,0) rather than NUMERIC?
Because a bare NUMERIC in MySQL means DECIMAL(10,0), which silently refuses an eleven-digit value. Spelling out the width avoids a load that fails partway through on the one row with a long id.
What do blank cells become?
NULL, in every column type. That is what a nullable column is for. Writing an empty string instead changes "we do not know" into "we know it is nothing", and a competing converter on this term does exactly that.
Why is my date column TEXT?
Because a converter cannot guarantee every value will parse, and a DATE column that rejects one row fails the whole statement. Excel dates come through as ISO strings and will load into a DATE column cleanly, so changing the one word in the CREATE TABLE is safe if you know the data.
Does it size text columns from my data?
No. No VARCHAR(27) fitted to the rows you happened to load. That runs today and truncates when the real export arrives. Text columns are TEXT, or VARCHAR(MAX) on SQL Server, and narrowing them is your call.
Is a comment added to the script?
No. Nothing is inserted into your output. Some free converters stamp a generated-by comment into every SQL file they produce, which then lives in your migrations directory permanently.
Get the sheet into the database
CREATE TABLE with real types, batched INSERTs, four dialects, NULL where it belongs.
Back to the converter