Skip to content
IntoSQL home

Convert a database to another database

Drop a dump from mysqldump, mariadb-dump, pg_dump, SSMS, SQL Developer or SQLcl, or a SQLite .db file, and download one script for PostgreSQL, MySQL, MariaDB, SQLite, SQL Server or Oracle with the same tables, keys, indexes and data. Every change is listed in a report, and the dump never leaves your computer.

Your file
sqlshop_2024.sqlSample file, 5.1 kB
Your database

18 rows converted, 8 fixes, none rejected

Show

Tables in shop_2024.sqlMySQL 8.0.46 dump · written by mysqldump · utf8mb4

  • customers8 rows3
    7 columns
    idbigint not null
    namevarchar(100) not null
    emailvarchar(150) not null
    countryvarchar(2) not null
    is_activeboolean not null
    signed_update
    created_attimestamptz(0) not null
  • orders10 rows5
    9 columns
    idbigint not null
    customer_idbigint not null
    statustext not null
    totalnumeric(10,2) not null
    itemsinteger not null
    ordered_attimestamp(0) not null
    shipped_attimestamp(0)
    notevarchar(255)
    updated_attimestamptz(0) not null
-- Written by IntoSQL from a MySQL dump. Load it with: psql -v ON_ERROR_STOP=1 -f <file>
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET synchronous_commit = off;
SET maintenance_work_mem = '512MB';

CREATE TABLE customers (
    id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    name varchar(100) NOT NULL,
    email varchar(150) NOT NULL,
    country varchar(2) DEFAULT 'US' NOT NULL,
    is_active boolean DEFAULT true NOT NULL,
    signed_up date,
    created_at timestamptz(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
    CONSTRAINT customers_id_check CHECK (id >= 0)
);

CREATE TABLE orders (
    id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    customer_id bigint NOT NULL,
    status text DEFAULT 'pending' NOT NULL,
    total numeric(10,2) NOT NULL,
    items integer DEFAULT 1 NOT NULL,
    ordered_at timestamp(0) NOT NULL,
    shipped_at timestamp(0),
    note varchar(255),
    updated_at timestamptz(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
    CONSTRAINT orders_id_check CHECK (id >= 0),
    CONSTRAINT orders_customer_id_check CHECK (customer_id >= 0),
    CONSTRAINT orders_status_check CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled', '')),
    CONSTRAINT orders_items_check CHECK (items >= 0)
);

COPY customers (id, name, email, country, is_active, signed_up, created_at) FROM stdin;
1	Ana Martínez	ana@example.com	MX	t	2023-01-15	2023-01-15 10:24:00+00:00
2	Zoë Müller	zoe@example.de	DE	t	2023-02-03	2023-02-03 08:11:42+00:00
3	José Peña	jose@example.es	ES	f	2023-03-21	2023-03-21 19:05:17+00:00
4	李雷	lilei@example.cn	CN	t	2023-05-09	2023-05-09 02:40:00+00:00
5	Noah O'Brien	noah@example.ie	IE	t	\N	2023-06-30 14:00:00+00:00
6	Amélie Laurent	amelie@example.fr	FR	t	2024-01-02	2024-01-02 09:30:00+00:00
7	Ravi Patel	ravi@example.in	IN	f	2024-02-29	2024-02-29 23:59:59+00:00
8	Sofia Rossi 🌻	sofia@example.it	IT	t	2024-04-12	2024-04-12 12:12:12+00:00
\.

COPY orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, updated_at) FROM stdin;
1	1	shipped	129.90	3	2024-03-01 10:00:00	2024-03-02 16:30:00	\N	2024-03-02 16:30:00+00:00
2	2	paid	54.00	1	2024-03-02 11:15:00	\N	Gift wrap 🎁	2024-03-02 11:20:00+00:00
3	1	cancelled	12.50	1	2024-03-03 09:45:00	\N	Customer changed their mind	2024-03-03 10:00:00+00:00
4	4	shipped	980.00	12	2024-03-05 02:10:00	2024-03-07 08:00:00	请小心轻放	2024-03-07 08:00:00+00:00
5	5	pending	45.75	2	2024-03-08 17:20:00	\N	Leave at the door; ring twice	2024-03-08 17:20:00+00:00
6	6	shipped	310.40	5	2024-03-10 13:00:00	2024-03-11 09:00:00	Fragile: "glass"	2024-03-11 09:00:00+00:00
7	8	paid	76.20	2	2024-03-11 18:45:00	\N	\N	2024-03-11 18:46:00+00:00
8	2		19.99	1	2024-03-12 08:00:00	\N	Status lost in an old import	2024-03-12 08:00:00+00:00
9	3	shipped	64.00	2	2024-03-14 15:30:00	\N	Shipping date never recorded	2024-03-15 10:00:00+00:00
10	7	paid	1499.00	1	2024-03-15 21:05:00	\N	C:\\Users\\ravi\\invoice.pdf	2024-03-15 21:05:00+00:00
\.


-- Keys, indexes and constraints, after the rows so each builds once.
CREATE UNIQUE INDEX customers_pkey ON customers (id);
CREATE UNIQUE INDEX customers_uq_email ON customers (email);
CREATE UNIQUE INDEX orders_pkey ON orders (id);
CREATE INDEX orders_idx_customer ON orders (customer_id);
CREATE INDEX orders_idx_status_date ON orders (status, ordered_at);
ALTER TABLE customers ADD CONSTRAINT customers_pkey PRIMARY KEY USING INDEX customers_pkey;
ALTER TABLE customers ADD CONSTRAINT customers_uq_email UNIQUE USING INDEX customers_uq_email;
ALTER TABLE orders ADD CONSTRAINT orders_pkey PRIMARY KEY USING INDEX orders_pkey;
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers (id) NOT VALID;
DO $f$ BEGIN
    ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_customer;
EXCEPTION WHEN foreign_key_violation THEN
    RAISE WARNING 'foreign key % has rows without a parent: it stays NOT VALID, and new rows are checked', 'fk_orders_customer';
END $f$;
SELECT setval(pg_get_serial_sequence('customers', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM customers)::numeric, 0) + 1, 1001), 9223372036854775807)::bigint, false);
COMMENT ON TABLE customers IS 'People who bought at least once';
SELECT setval(pg_get_serial_sequence('orders', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM orders)::numeric, 0) + 1, 5001), 9223372036854775807)::bigint, false);
CREATE FUNCTION orders_updated_at_on_update() RETURNS trigger LANGUAGE plpgsql AS $f$
BEGIN
    IF NEW IS DISTINCT FROM OLD AND NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at THEN
        NEW.updated_at := CURRENT_TIMESTAMP;
    END IF;
    RETURN NEW;
END
$f$;
CREATE TRIGGER orders_updated_at_on_update BEFORE UPDATE ON orders FOR EACH ROW EXECUTE FUNCTION orders_updated_at_on_update();

Fixes

  1. "id" of customers is unsigned, so it became bigint with a check that it isn't negative.
  2. "signed_up" of customers allows NULL now: some of its values couldn't be stored and are NULL.
  3. The default of "signed_up" in customers, 0000‑00‑00, isn't a date PostgreSQL accepts, so the column has no default.
  4. "customer_id" of orders is unsigned, so it became bigint with a check that it isn't negative.
  5. "id" of orders is unsigned, so it became bigint with a check that it isn't negative.
  6. "items" of orders is unsigned, so it became integer with a check that it isn't negative.
  7. 1 row of "status" in orders holds the empty value MySQL stores for invalid entries; the check of its values allows it.
  8. "updated_at" of orders changes to the current time on every update; a trigger does that in PostgreSQL.

Needs a look

  • 1 value in "signed_up" of customers is a zero or impossible date such as 0000‑00‑00, which PostgreSQL can't store, so it is NULL. If those dates mean something, replace them in the source with real ones before dumping.
  • 1 value in "shipped_at" of orders is a zero or impossible date such as 0000‑00‑00 00:00:00, which PostgreSQL can't store, so it is NULL. If those dates mean something, replace them in the source with real ones before dumping.

Your file is converted on this computer and never uploaded.

Other samples:

Every database pair

Rows are the database the dump comes from, columns the database the script is for. Each pair has its own page with the converter set up for it, the rules it applies and how to load the script.

Data types for every pair

What is kept and what is reported

The converter reads the dump itself, without a database server, and writes the script from what it holds. Each pair's page lists the rules it applies, between 25 and 67 per pair, with the report line each one writes.

Kept in the script

  • Tables and columns, each with the closest type the target has and its length and precision.
  • Primary, unique and foreign keys, indexes and defaults.
  • AUTO_INCREMENT, identity and serial columns, with each row's id and the counter continuing where the dump left it.
  • Every row, with dates, decimals, text and binary values as the dump holds them.

Listed in the report

  • Values the target can't hold as they are, such as a 0000-00-00 date in PostgreSQL, with the column and how many.invalid_date_null
  • Types the target doesn't have, such as ENUM or INET6, with the type each one became.type_replaced
  • Views, triggers, procedures, functions and events, by name: they are written in each database's own language, so they aren't converted.not_converted_objects
  • Text that isn't valid in its character set, and dumps that already lost their 4-byte characters.invalid_utf8_replaced

Measured against pgloader

The same dumps, converted and loaded by IntoSQL and by pgloader 3.6.7 on one machine (a 10-core i9-10900K with a SATA SSD), in October 2026.

  • A TINYINT(1) column holding 2, -1 and -5: pgloader loads true for each one, without a warning. IntoSQL keeps the numbers and the report says why.
  • A dump with a default of 'it's', a table named with a backtick, an ENUM holding the empty value and a TIME over 24 hours: pgloader stops. IntoSQL converts it and loads it.
  • What pgloader loads has lost its primary, unique and foreign keys and its identities. IntoSQL keeps every one.
  • A 5.3 GB dump of 30 million rows: pgloader needs it restored into a running MySQL first, which takes 18 minutes, then runs out of memory with its defaults. IntoSQL converts the dump in 10 seconds, and psql loads the script in about 3 minutes.
  • Across 476 dumps from MySQL 5.5 to 9 and MariaDB 10.3 to 12, loaded into PostgreSQL 13 and 18: no load errors, and no value changed without a line in the report.
The MySQL to PostgreSQL page

Export the database you have

The converter reads a file, not a running server. Each database writes one with its own tools:

Questions about database migration

Not answered here? Write to us from the contact page and a person replies. Contact page

Is my dump uploaded?

No. The converter runs in your browser and reads the dump from your disk in pieces, so it never leaves your computer. The desktop app and the command line convert on your computer too.

How large can the dump be?

The converter never holds the whole dump in memory: it reads the file twice, by ranges, and writes the script as it goes. Multi-gigabyte dumps convert in the browser, in the desktop app and with the command line.

Which versions can the dump come from?

MySQL 5.5 to 9, MariaDB 10.3 to 12, PostgreSQL 10 and later, SQL Server 2017 and later, SQLite 3.0 and later, and Oracle 11g and later. Each pair's page says which versions its script loads on.

Do I need a running database server?

No. The converter reads the dump file itself, so the source database doesn't have to run anywhere. The script loads with the target's own client, such as psql, mysql or sqlcmd.

What isn't converted?

Views, triggers, stored procedures, functions and events are written in each database's own language, so they are listed in the report by name instead. The tables, keys, indexes and rows are all in the script.

Can it load the script straight into my database?

The desktop app and the intosql load command write straight into SQLite for free, and into PostgreSQL, MySQL and MariaDB with IntoSQL Pro. For SQL Server and Oracle, run the script with sqlcmd or sqlplus.