
Stop Writing INSERT Statements by Hand: Use a JSON to SQL Converter
📷 Christina Morillo / PexelsStop Writing INSERT Statements by Hand: Use a JSON to SQL Converter
Manually turning a 200-row JSON export into INSERT statements is painful and error-prone. Here's how to automate it and what to watch out for.
You exported 200 rows of user data from a third-party service. The format is JSON. Your database needs SQL INSERT statements. And you are, for some reason, thinking about writing them by hand.
That thought process goes roughly: open the JSON file, look at the structure, write out the CREATE TABLE statement, then start on the INSERT statements. The first one takes two minutes. The second takes a minute. By the tenth you have a macro going. By the fiftieth you've introduced a typo you won't notice until the import fails with a constraint error.
There's a better way. The JSON to SQL converter takes your JSON array, infers the schema, and produces ready-to-run SQL — INSERT statements in bulk, with the right column names and quoted values. This guide walks through what it does, how to use it effectively, and where you'll still need to do some manual cleanup.
What the Tool Actually Does
At a high level: you paste in a JSON array of objects, select a SQL dialect, and the tool generates two things — a CREATE TABLE statement based on the inferred schema, and a series of INSERT statements for your data.
The type inference is the interesting part. The tool looks at the values in your JSON and makes reasonable guesses about SQL types. Strings become VARCHAR or TEXT. Numbers become INT or FLOAT. Booleans become BOOLEAN or TINYINT(1) depending on your dialect. Null values default to nullable VARCHAR.
It also handles the formatting differences between SQL dialects automatically — backtick-quoted column names for MySQL, double-quoted identifiers for PostgreSQL, NVARCHAR for SQL Server text columns.
A Realistic Example
Say you have a JSON export from a SaaS app. The users table export looks like this:
[
{
"id": 1,
"name": "Alice Chen",
"email": "alice@example.com",
"age": 31,
"active": true,
"created_at": "2025-01-15T09:23:00Z"
},
{
"id": 2,
"name": "Ben Kowalski",
"email": "ben@example.com",
"age": 28,
"active": false,
"created_at": "2025-02-03T14:55:00Z"
},
{
"id": 3,
"name": "Sara Okonkwo",
"email": "sara@example.com",
"age": 34,
"active": true,
"created_at": "2025-03-11T08:00:00Z"
}
]
Paste this in, select MySQL, and the output looks like:
CREATE TABLE `users` (
`id` INT,
`name` VARCHAR(255),
`email` VARCHAR(255),
`age` INT,
`active` BOOLEAN,
`created_at` VARCHAR(255)
);
INSERT INTO `users` (`id`, `name`, `email`, `age`, `active`, `created_at`) VALUES
(1, 'Alice Chen', 'alice@example.com', 31, TRUE, '2025-01-15T09:23:00Z'),
(2, 'Ben Kowalski', 'ben@example.com', 28, FALSE, '2025-02-03T14:55:00Z'),
(3, 'Sara Okonkwo', 'sara@example.com', 34, TRUE, '2025-03-11T08:00:00Z');
For 200 rows you'd get that same structure, but with a batch of 200 values. That is the output of maybe thirty seconds of pasting and clicking, versus fifteen minutes of error-prone typing.
Notice a few things in this output. The created_at field is typed as VARCHAR(255) rather than DATETIME — we'll get to why that matters. The table name defaults to users based on a generic guess; the tool may let you specify it. The CREATE TABLE doesn't include PRIMARY KEY or NOT NULL constraints — those are your job to add.
Dialect Differences Worth Knowing
The "SQL dialect" selector matters more than it might seem. Syntax differences between databases are small but they will cause errors if you pick the wrong one.
MySQL uses backticks to quote identifiers: `column_name`. This is important if your column name happens to collide with a MySQL reserved word (like order, key, index, or values). MySQL also uses TINYINT(1) for booleans in older schemas, though BOOLEAN works in MySQL 5.7+.
PostgreSQL uses double quotes for identifiers: "column_name". It has native BOOLEAN and TIMESTAMP WITH TIME ZONE types. It also has SERIAL and BIGSERIAL for auto-incrementing primary keys, which is different from MySQL's AUTO_INCREMENT.
SQLite is permissive about types — SQLite uses "type affinity" rather than strict types, so a column declared as TEXT can store integers and vice versa. For SQLite output you'll often see simpler type names like TEXT and INTEGER.
SQL Server uses NVARCHAR instead of VARCHAR for Unicode text strings, and square bracket quoting for identifiers: [column_name]. SQL Server also uses BIT for boolean values rather than BOOLEAN or TINYINT.
If you're importing into a specific database, select that dialect. If you'll be running the SQL manually and adapting it, any dialect gives you a usable starting point — just be aware of what you'll need to change.
Type Inference: What It Gets Right and Where It Fails
The tool is pretty good at the common cases:
123in JSON becomesINTin SQL123.45becomesFLOATorDECIMAL"hello"becomesVARCHAR(255)orTEXTtrue/falsebecomesBOOLEANorTINYINT(1)nullbecomes a nullable column, usually typed asVARCHAR
Where it fails is anywhere JSON uses strings to represent non-string data.
Phone numbers are a classic trap. If your JSON has "phone": "12345", that's a string in JSON, and you'll get VARCHAR(255) — which is correct, since phone numbers should be stored as strings (leading zeros, country codes with +, formatted numbers). But if someone stored phone numbers as bare integers in the source system, you might see "phone": 12345 and get INT. Then your import silently drops leading zeros and formatting.
Date and time fields will almost always come out as VARCHAR because JSON has no native date type. "2025-01-15T09:23:00Z" is just a string as far as JSON is concerned. If you need a proper DATETIME or TIMESTAMP column, change the column type in the CREATE TABLE statement after generating it. The data itself will still import correctly since SQL will parse the ISO 8601 string when inserting into a TIMESTAMP column.
IDs that look like numbers are trickier. An id field of value 1042 will be typed as INT. That might be fine — or your system might use UUIDs or alphanumeric IDs that just happen to be all-numeric for the first few rows in your sample. If row 201 has id: "a1b2c3", your import is going to fail.
Large text fields will be typed as VARCHAR(255). If a field contains longer content — article bodies, descriptions, serialized data — you'll need to change the type to TEXT or LONGTEXT in MySQL, or TEXT in PostgreSQL (which has no length limit for text).
The rule of thumb: always review the generated CREATE TABLE statement before running it. The INSERT statements are usually fine, but the schema inference is where human judgment is needed.
Batch Inserts: Why They Matter
For small datasets — ten or twenty rows — it doesn't much matter whether you insert one row at a time or in batches. For larger datasets, the difference is significant.
A single-row INSERT looks like this:
INSERT INTO `users` (`id`, `name`, `email`) VALUES (1, 'Alice', 'alice@example.com');
INSERT INTO `users` (`id`, `name`, `email`) VALUES (2, 'Ben', 'ben@example.com');
-- ... 198 more statements
A batch INSERT looks like this:
INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Alice', 'alice@example.com'),
(2, 'Ben', 'ben@example.com'),
(3, 'Sara', 'sara@example.com'),
-- ... up to 100 rows
;
INSERT INTO `users` (`id`, `name`, `email`) VALUES
(101, 'David', 'david@example.com'),
-- ... next batch
;
The batch version is faster for two reasons. First, there's one network round trip per batch instead of one per row. Second, the database can optimize a single large write better than many small ones — transaction overhead, index updates, and write-ahead logging all happen once per statement rather than once per row.
The reason converters typically cap batches at 100-500 rows rather than putting everything in one statement is memory and reliability. A 10,000-row single INSERT statement can hit memory limits, be slow to parse, and if it fails partway through you have no useful feedback about which rows were the problem. Smaller batches are more practical.
What the Tool Won't Do
Being clear about limitations saves you a debugging session later.
No foreign keys. The converter knows nothing about relationships between tables. If your JSON has a user_id field referencing another table, you get a plain INT column — no FOREIGN KEY constraint, no REFERENCES clause. You'll need to add those manually.
No indexes. The generated CREATE TABLE has no index definitions. If you're importing a large dataset and then querying it, you'll want to add INDEX or UNIQUE INDEX definitions on columns you'll be filtering or joining on. This is always a post-import step.
No primary key marking. The tool might create an id INT column but it won't add PRIMARY KEY to it or AUTO_INCREMENT. If you want id to be a proper auto-incrementing primary key, add PRIMARY KEY AUTO_INCREMENT to that column definition in MySQL, or switch to SERIAL in PostgreSQL.
No complex or nested types. If a field value is an object ("address": {"street": "123 Main St", "city": "Portland"}) or an array ("tags": ["developer", "remote"]), the tool can't flatten that into a relational schema. It'll typically either serialize it as a JSON string or skip the field. You need to decide: store it as a JSON column (MySQL 5.7+ and PostgreSQL support native JSON columns), flatten it into separate columns, or normalize it into a separate table.
No upsert logic. The generated statements are plain INSERT with no conflict handling. If you run them against a table that already has data with overlapping primary keys, you'll get constraint violation errors. For idempotent imports, you'd need INSERT IGNORE in MySQL, ON CONFLICT DO NOTHING in PostgreSQL, or MERGE in SQL Server — none of which the tool adds automatically.
No transactions. The output isn't wrapped in BEGIN TRANSACTION / COMMIT. For large imports, wrapping the whole thing in a transaction is good practice — if something fails midway, you can roll back cleanly. Add it yourself around the generated statements.
When to Use This vs. Other Options
The JSON to SQL converter is the right tool for a specific set of situations. It's not the right tool for everything.
Use it when:
- You have a one-time data import from an external source
- You're migrating data between databases and need a quick SQL dump
- You're setting up a demo or test database with realistic-looking data
- You're working directly with SQL and don't have an application stack running
- You need to share data with someone who only has SQL access
Reach for your ORM or ETL tool instead when:
- You need repeatable, version-controlled seed data — tools like Prisma's
db seed, Django'sfixtures, or Rails' seed files integrate with your migration workflow and can be re-run safely - The import is complex and involves transforming data, not just inserting it
- You need to handle relationships and foreign key constraints programmatically
- You're doing this regularly on a schedule — write a proper ETL script or use a tool like Apache NiFi, dbt, or even a simple Python script with pandas and sqlalchemy
The JSON to SQL converter is essentially a shortcut for the "I just need to get this data into a table" scenario. When the scenario is more complex than that, you need a more complex tool.
A Practical Workflow
Here's the sequence that works well:
-
Format your JSON first. If your export is minified, paste it through the JSON formatter first. This lets you review the structure and catch any issues before converting.
-
Check the array structure. The converter expects a JSON array of objects where every object has the same top-level keys. If your export is wrapped in a root object (
{"data": [...]}) rather than being a bare array, extract the array first. -
Select your dialect and paste the JSON in. Copy the generated SQL.
-
Review the CREATE TABLE. Before running anything, read through the column types and fix any that look wrong — especially date fields, large text fields, and any numeric field that should actually be a string.
-
Add constraints you need. Mark the primary key, add
NOT NULLwhere appropriate, addUNIQUEon email columns, and add anyFOREIGN KEYconstraints. -
Run the CREATE TABLE first, then the INSERTs. If the table already exists and you're reimporting, decide whether to
DROP TABLE IF EXISTSfirst or use some conflict resolution strategy. -
Verify row counts. After the import, run
SELECT COUNT(*) FROM your_table;and compare to the number of rows in your JSON. If they match, you're done.
Formatting the Output SQL
The generated SQL is usually readable but not always formatted to your standards. If you want the INSERT statements formatted consistently — aligned columns, consistent casing for keywords — run the output through the SQL formatter. It won't change the semantics but it makes the SQL easier to review and diff.
Summary
Writing INSERT statements by hand for large JSON datasets is one of those tasks where automation makes the most sense. The JSON to SQL converter handles the mechanical conversion: it reads your array, infers types, generates a schema, and produces batch INSERT statements in your target dialect.
The parts that still need your attention are schema review (types, constraints, indexes), handling of nested data, and making the INSERT logic idempotent if you need to re-run it. The tool does the 90% that is purely mechanical. The remaining 10% is database design judgment that no automation can replace.
If you're working with JSON data regularly, two other tools are worth bookmarking alongside this one: the JSON formatter for reviewing and cleaning your input, and the SQL formatter for tidying up generated output before committing it to a migration file.