Markdown Table to SQL Converter

Load a documented table into a database. You get a CREATE TABLE with types read from the data and INSERT statements batched to a size your client will accept.

To convert a Markdown table to SQL, paste the table above and pick a dialect. You get a CREATE TABLE whose column types are inferred from all the values in each column, followed by INSERT statements batched at a size you choose. Empty cells become NULL rather than empty strings, and identifiers are quoted the way your dialect spells them.

Want to clean the rows before you load them? Open the app

Reference data lives in documents and belongs in tables

Every codebase has a handful of lookup tables that started life as a table in a wiki page: shipping zones, plan tiers, error code meanings, tax bands. Someone maintains the document because it is easy to edit and easy to review, and then the application needs the same data in a database.

The other case is a migration. You are writing the seed file for a new service and the source of truth is a table in the spec. Typing it out is slow and typing it out wrong is worse.

What both need from a converter is not just INSERT statements but a CREATE TABLE that will not need fixing, which means the column types have to come from the data rather than from a default of TEXT everywhere.

Worked example, in Postgres

The release table:

| service | version | released | replicas | notes |
|:--------|--------:|:--------:|---------:|:------|
| billing-api | 2.4.1 | 2026-01-08 | 6 | rollout paused |
| web-frontend | 5.0.0 | 2026-01-12 | 4 | canary 10% \| full 14 Jan |
| search-index | 1.19.3 | 2026-01-15 | 12 | |

And the script:

CREATE TABLE "releases" (
  "service" TEXT,
  "version" TEXT,
  "released" TEXT,
  "replicas" NUMERIC,
  "notes" TEXT
);

INSERT INTO "releases" ("service", "version", "released", "replicas", "notes") VALUES
  ('billing-api', '2.4.1', '2026-01-08', 6, 'rollout paused'),
  ('web-frontend', '5.0.0', '2026-01-12', 4, 'canary 10% | full 14 Jan'),
  ('search-index', '1.19.3', '2026-01-15', 12, NULL);

The empty notes cell is NULL, not ''. That is a real distinction in SQL, it is what a nullable column is for, and a competing converter writes the empty string here, which quietly turns "we do not know" into "we know it is nothing".

Four dialects, and why the differences are not cosmetic

A script that gets the dialect wrong fails on its first statement. The identifier quote character is different: double quotes in Postgres and SQLite, backticks in MySQL, square brackets in SQL Server. The boolean literal is different: TRUE in Postgres, 1 in MySQL and SQL Server. The text type is different: TEXT almost everywhere, VARCHAR(MAX) in SQL Server.

The exact-number type is the fiddliest. A bare NUMERIC in MySQL means DECIMAL(10,0), which silently refuses an eleven-digit id, so MySQL gets DECIMAL(20,0) spelled out. SQL Server's NUMERIC defaults to (18,0), which is wide enough for anything, and is written explicitly anyway so the script says what it means.

There is no VARCHAR(n) sized from your data anywhere in the output. A competing converter emits name VARCHAR(27) derived from a six-row sample, which runs perfectly and then truncates the moment you load the real export.

Batching

Rows are grouped into multi-row INSERT statements, five hundred at a time by default. One statement per row is slower by an order of magnitude on any real load; one statement for a million rows exceeds what most clients will send and most servers will parse.

SQL Server caps a multi-row VALUES clause at a thousand rows, so choosing that dialect clamps the batch and tells you it did. Nothing in the script fails at run time because of a setting the page let you choose.

The CREATE TABLE can be turned off if the table already exists, which is the common case for reference data that is being refreshed rather than created.

What the types are, and what they are not

  • A column is numeric only when every value in it round trips exactly. A column of version strings is text, which is right. A column of counts is numeric.
  • A column is boolean only when every value is lowercase true or false. A column of Y and N is text, because turning Y into TRUE would lose the difference between Y, y and yes.
  • Dates arrive as text in the CREATE TABLE, deliberately. A DATE column is the right choice if you are sure the format is consistent, and this cannot be sure. Change the type in the script before you run it; it is one word.
  • Nothing is sized from a sample. No VARCHAR(27), no NUMERIC(6,2). A generated width fitted to the rows you happened to paste is a trap that springs later.

Frequently Asked Questions

Which SQL dialects are supported?

Postgres, MySQL, SQLite and SQL Server. The choice changes the identifier quoting, the boolean literal and the numeric and text types, all of which will fail the first statement if they are wrong. Most free converters on this term offer one dialect and hope.

What happens to an empty cell?

It becomes NULL, not an empty string, in every column type. That is what a nullable column is for. Writing '' instead turns "we do not know" into "we know it is nothing", which is a different fact.

Does it size VARCHAR columns from my data?

No, deliberately. A VARCHAR(27) derived from the rows you pasted runs fine now and truncates when the real export arrives. Text columns are TEXT, or VARCHAR(MAX) on SQL Server, and you can narrow them yourself if you know the true bound.

Why is my date column TEXT rather than DATE?

Because a converter cannot be sure every value in it will parse the same way, and a DATE column that rejects one row fails the whole load. If you know the format is consistent, changing TEXT to DATE in the CREATE TABLE is a one-word edit.

How many rows go into one INSERT?

Five hundred by default, adjustable. One statement per row is much slower on a real load and one statement for everything exceeds what most clients will send. Choosing SQL Server clamps the batch to its thousand-row ceiling and says so.

Can I skip the CREATE TABLE?

Yes, one switch gives you INSERT statements only. That is the usual choice when the table already exists and you are refreshing reference data rather than creating it.

Load the documented table into a database

CREATE TABLE with real types, batched INSERTs, four dialects, NULL where it belongs.

Back to the converter