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.
18 rows converted, 8 fixes, none rejected
Tables in shop_2024.sqlMySQL 8.0.46 dump · written by mysqldump · utf8mb4
customers8 rows3
7 columns id bigint not null name varchar(100) not null email varchar(150) not null country varchar(2) not null is_active boolean not null signed_up date created_at timestamptz(0) not null orders10 rows5
9 columns id bigint not null customer_id bigint not null status text not null total numeric(10,2) not null items integer not null ordered_at timestamp(0) not null shipped_at timestamp(0) note varchar(255) updated_at timestamptz(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
- "id" of customers is unsigned, so it became bigint with a check that it isn't negative.
- "signed_up" of customers allows NULL now: some of its values couldn't be stored and are NULL.
- The default of "signed_up" in customers, 0000‑00‑00, isn't a date PostgreSQL accepts, so the column has no default.
- "customer_id" of orders is unsigned, so it became bigint with a check that it isn't negative.
- "id" of orders is unsigned, so it became bigint with a check that it isn't negative.
- "items" of orders is unsigned, so it became integer with a check that it isn't negative.
- 1 row of "status" in orders holds the empty value MySQL stores for invalid entries; the check of its values allows it.
- "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.
- MySQL
- MariaDB
- PostgreSQL
- SQL Server
- SQLite
- Oracle
| from ↓ to → | PostgreSQL | MySQL | MariaDB | SQLite | SQL Server | Oracle |
|---|---|---|---|---|---|---|
| MySQL | MySQL to PostgreSQL | MySQL to MySQL | MySQL to MariaDB | MySQL to SQLite | MySQL to SQL Server | MySQL to Oracle |
| MariaDB | MariaDB to PostgreSQL | MariaDB to MySQL | MariaDB to MariaDB | MariaDB to SQLite | MariaDB to SQL Server | MariaDB to Oracle |
| PostgreSQL | PostgreSQL to PostgreSQL | PostgreSQL to MySQL | PostgreSQL to MariaDB | PostgreSQL to SQLite | PostgreSQL to SQL Server | PostgreSQL to Oracle |
| SQL Server | SQL Server to PostgreSQL | SQL Server to MySQL | SQL Server to MariaDB | SQL Server to SQLite | SQL Server to SQL Server | SQL Server to Oracle |
| SQLite | SQLite to PostgreSQL | SQLite to MySQL | SQLite to MariaDB | SQLite to SQLite | SQLite to SQL Server | SQLite to Oracle |
| Oracle | Oracle to PostgreSQL | Oracle to MySQL | Oracle to MariaDB | Oracle to SQLite | Oracle to SQL Server | Oracle to Oracle |
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.
Export the database you have
The converter reads a file, not a running server. Each database writes one with its own tools:
MySQL
A dump from mysqldump, mysqlpump, phpMyAdmin or Adminer, as .sql or .sql.gz. --default-character-set=utf8mb4 --hex-blob --routines --triggers --events keep the most.
MySQL to PostgreSQL migration guideMariaDB
A dump from mariadb-dump, mysqldump, phpMyAdmin or Adminer, as .sql or .sql.gz, with the same options as MySQL.
MariaDB to PostgreSQL migration guidePostgreSQL
A plain dump from pg_dump or pg_dumpall. A custom archive (-Fc) becomes one with pg_restore -f dump.sql, without a server.
PostgreSQL to MySQL migration guideSQL Server
The script SSMS's Generate Scripts writes, with Types of data to script set to Schema and data. dbatools and mssql-scripter write the same.
SQL Server to PostgreSQL migration guideSQLite
The database file itself (.db, .sqlite, .sqlite3), or the SQL that sqlite3 app.db .dump writes.
SQLite to PostgreSQL migration guideOracle
The .sql file SQL Developer's Database Export or SQLcl writes, with DDL and Insert statements. A .dmp file from exp or expdp has to be imported and exported again first.
Oracle to PostgreSQL migration guide
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.