Skip to content
IntoSQL home

Move a SQLite database to an older or newer SQLite

Drop a SQLite database file (.db, .sqlite, .sqlite3) or the SQL that sqlite3's .dump writes, and download a script that loads on SQLite 3.31 and later. Each column keeps its declared type and collation, STRICT is left out so older versions accept the tables, and every change is listed in the report.

Your file
sqlshop_2024_sqlite.sqlSample file, 3.4 kB
Your database

18 rows converted, no fixes, none rejected

Show

Tables in shop_2024_sqlite.sqlSQLite dump · written by sqlite3 · utf-8

  • customers8 rows
    7 columns
    idINTEGER not null
    nameTEXT not null
    emailTEXT not null
    countryTEXT not null
    is_activeINTEGER not null
    signed_upDATE
    created_atDATETIME not null
  • orders10 rows
    9 columns
    idINTEGER not null
    customer_idINTEGER not null
    statusTEXT not null
    totalDECIMAL(10,2) not null
    itemsINTEGER not null
    ordered_atDATETIME not null
    shipped_atDATETIME
    noteTEXT
    receiptBLOB

Not converted

  • View order_totals
  • Trigger orders_shipped

Each database writes these in its own language. Rewrite them for SQLite.

-- Written by IntoSQL from a SQLite dump. Load it with: sqlite3 -bail <database> < <file>
PRAGMA foreign_keys = OFF;
PRAGMA synchronous = OFF;
BEGIN;

CREATE TABLE customers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT COLLATE NOCASE NOT NULL,
    country TEXT DEFAULT 'US' NOT NULL,
    is_active INTEGER DEFAULT 1 NOT NULL,
    signed_up DATE,
    created_at DATETIME DEFAULT (datetime('now', 'localtime')) NOT NULL,
    CONSTRAINT customers_is_active_check CHECK (is_active IN (0, 1)),
    CONSTRAINT customers_check1 CHECK (length(country) = 2)
);

CREATE TABLE orders (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    customer_id INTEGER NOT NULL,
    status TEXT DEFAULT 'pending' NOT NULL,
    total DECIMAL(10,2) NOT NULL,
    items INTEGER DEFAULT 1 NOT NULL,
    ordered_at DATETIME NOT NULL,
    shipped_at DATETIME,
    note TEXT,
    receipt BLOB,
    CONSTRAINT orders_check1 CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled')),
    CONSTRAINT orders_fkey FOREIGN KEY (customer_id) REFERENCES customers (id) ON DELETE CASCADE
);

INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(1,'Ana Martínez','ana@example.com','MX',1,'2023-01-15','2023-01-15 10:24:00');
INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(2,'Zoë Müller','zoe@example.de','DE',1,'2023-02-03','2023-02-03 08:11:42');
INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(3,'José Peña','jose@example.es','ES',0,'2023-03-21','2023-03-21 19:05:17');
INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(4,'李雷','lilei@example.cn','CN',1,'2023-05-09','2023-05-09 02:40:00');
INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(5,'Noah O''Brien','noah@example.ie','IE',1,NULL,'2023-06-30 14:00:00');
INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(6,'Amélie Laurent','amelie@example.fr','FR',1,'2024-01-02','2024-01-02 09:30:00');
INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(7,'Ravi Patel','ravi@example.in','IN',0,'2024-02-29','2024-02-29 23:59:59');
INSERT INTO customers (id, name, email, country, is_active, signed_up, created_at) VALUES
(8,'Sofia Rossi 🌻','sofia@example.it','IT',1,'2024-04-12','2024-04-12 12:12:12');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(1,1,'shipped',129.9,3,'2024-03-01 10:00:00','2024-03-02 16:30:00',NULL,NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(2,2,'paid',54,1,'2024-03-02 11:15:00',NULL,'Gift wrap 🎁',NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(3,1,'cancelled',12.5,1,'2024-03-03 09:45:00',NULL,'Customer changed their mind',NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(4,4,'shipped',980,12,'2024-03-05 02:10:00','2024-03-07 08:00:00','请小心轻放',X'25504446');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(5,5,'pending',45.75,2,'2024-03-08 17:20:00',NULL,'Leave at the door;
ring twice',NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(6,6,'shipped',310.4,5,'2024-03-10 13:00:00','2024-03-11 09:00:00','Fragile: "glass"',NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(7,8,'paid',76.2,2,'2024-03-11 18:45:00',NULL,NULL,NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(8,2,'paid',19.99,1,'2024-03-12 08:00:00',NULL,'Paid at the counter',NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(9,3,'shipped',64,2,'2024-03-14 15:30:00',NULL,'Shipping date never recorded',NULL);
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, receipt) VALUES
(10,7,'paid',1499,1,'2024-03-15 21:05:00',NULL,'C:\Users\ravi\invoice.pdf',NULL);

-- Indexes, triggers and counters, after the rows so each index builds once.
CREATE UNIQUE INDEX customers_email ON customers (email);
CREATE INDEX orders_idx_orders_customer ON orders (customer_id);
CREATE INDEX orders_idx_orders_status_date ON orders (status, ordered_at);
UPDATE sqlite_sequence SET seq = max(seq, 8) WHERE name = 'customers';
INSERT INTO sqlite_sequence (name, seq) SELECT 'customers', 8 WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = 'customers');
UPDATE sqlite_sequence SET seq = max(seq, 10) WHERE name = 'orders';
INSERT INTO sqlite_sequence (name, seq) SELECT 'orders', 10 WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = 'orders');
COMMIT;

-- Rows whose foreign key finds no parent, if any: the load doesn't stop for them.
PRAGMA foreign_key_check;

Fixes

Nothing needed fixing.

Needs a look

  • 2 views, triggers, routines or events aren't converted; the report lists each one. Recreate them by hand in SQLite.

Your file is converted on this computer and never uploaded.

Other samples:

What changes between the file and the script

Each row is a rule the converter applies, with the report line it writes when the rule changes something in your data.

  • declared types (any text: VARCHAR(255), DATETIME, BOOLEAN, JSON or none)

    the type every value in the column fits

    The same declared types, so the values load as they were stored.

  • BOOLEAN, BOOL

    INTEGER 0 or 1 with a CHECK

    When every value is 0 or 1; a column that also holds other values keeps them as numbers or text.

  • TEXT, VARCHAR(n), CHAR(n), CLOB

    the declared type, kept as it is

    SQLite never enforces a length; a declared length holds only when every value fits it.

  • DECIMAL(p,s), NUMERIC

    the exact type when every value fits it, else a double

    SQLite stores these as integers or doubles; a declared precision holds only when the values fit it.

  • BLOB, and columns holding bytes

    BLOB

    Bytes stay bytes; text and numbers in the same column are kept as their bytes.

  • DATE, DATETIME, TIMESTAMP holding ISO 8601 text

    DATE, DATETIME as ISO 8601 text

    When every value is a date or a date and time; epoch numbers and other text keep their values as they are.

  • JSON holding JSON text

    TEXT

    When every value is a JSON object or array.

  • Infinity in REAL columns

    9e999, SQLite's own infinity

    SQLite writes infinity as an overflowing literal, which loads back as infinity.

  • INTEGER PRIMARY KEY, AUTOINCREMENT

    INTEGER PRIMARY KEY, AUTOINCREMENT kept

    Only a column declared exactly INTEGER PRIMARY KEY is the rowid; every row keeps its id, and the counter starts after the highest id ever handed out.

  • COLLATE NOCASE

    COLLATE NOCASE

    The same collation.

  • generated columns

    generated columns, STORED or VIRTUAL as in the source

    SQLite computes them the same way.

  • generated columns with functions IntoSQL doesn't write, and the || operator

    a plain column

    A formula that can't be written leaves a plain column and a report line; the dump has no values for generated columns.

    Report line: generated_not_translated

  • defaults with functions IntoSQL doesn't write (strftime, randomblob)

    no default

    A default that can't be written is left out and reported; CURRENT_TIMESTAMP and datetime('now') are written.

    Report line: default_not_translated

  • CHECK constraints

    translated

    The same checks, tested as each row loads.

  • CHECK constraints with functions IntoSQL doesn't write

    not created

    A check that can't be written is left out and reported.

    Report line: check_skipped

  • partial and expression indexes

    not created

    IntoSQL writes indexes on whole columns; create the others again after loading.

    Report line: index_skipped

  • foreign keys (unenforced unless PRAGMA foreign_keys=ON)

    the same foreign keys, listed by PRAGMA foreign_key_check

    SQLite checks foreign keys only when told to, so rows without a parent are common: they load, and the key is left unchecked with a warning instead of stopping the load.

  • foreign keys to tables the dump doesn't create

    not created

    A key can't name a table that isn't there.

    Report line: foreign_key_skipped

  • primary keys holding NULL, and NOT NULL columns with values that become NULL

    NOT NULL dropped

    SQLite lets a primary key other than the rowid hold NULL; the column can't be NOT NULL in the target, or the load would stop.

    Report line: not_null_relaxed

  • views, triggers and virtual tables (FTS, R*Tree)

    listed in the report

    Their code is written in SQLite's own language, and virtual tables need SQLite's modules; they are listed for you to recreate. Their internal tables aren't copied.

    Report line: not_converted_objects

  • dumps without CREATE TABLE (--data-only)

    types read from the values

    A data-only dump has only rows, so the types come from the values and the columns are numbered.

    Report line: no_schema

  • expressions in INSERT values other than the text functions dumps write

    NULL

    unistr(), replace(), char() and || are evaluated as the shell writes them; other expressions can't be without a database.

    Report line: expression_value

  • NUL characters in text

    kept

    The sqlite3 shell and Python cut text at a NUL character when they dump it, so only what precedes it is in the dump.

  • text that isn't valid UTF-8

    the bad bytes as the replacement character

    Dumps write such text as raw bytes; the script is UTF-8.

    Report line: invalid_utf8_replaced

  • text decoded twice (ñ)

    copied as it is

    The dump holds exactly these characters, so they are copied; the report points them out.

    Report line: mojibake_suspected

In the fileIn the scriptReport line
declared types (any text: VARCHAR(255), DATETIME, BOOLEAN, JSON or none)the type every value in the column fitsThe same declared types, so the values load as they were stored.
BOOLEAN, BOOLINTEGER 0 or 1 with a CHECKWhen every value is 0 or 1; a column that also holds other values keeps them as numbers or text.
TEXT, VARCHAR(n), CHAR(n), CLOBthe declared type, kept as it isSQLite never enforces a length; a declared length holds only when every value fits it.
DECIMAL(p,s), NUMERICthe exact type when every value fits it, else a doubleSQLite stores these as integers or doubles; a declared precision holds only when the values fit it.
BLOB, and columns holding bytesBLOBBytes stay bytes; text and numbers in the same column are kept as their bytes.
DATE, DATETIME, TIMESTAMP holding ISO 8601 textDATE, DATETIME as ISO 8601 textWhen every value is a date or a date and time; epoch numbers and other text keep their values as they are.
JSON holding JSON textTEXTWhen every value is a JSON object or array.
Infinity in REAL columns9e999, SQLite's own infinitySQLite writes infinity as an overflowing literal, which loads back as infinity.
INTEGER PRIMARY KEY, AUTOINCREMENTINTEGER PRIMARY KEY, AUTOINCREMENT keptOnly a column declared exactly INTEGER PRIMARY KEY is the rowid; every row keeps its id, and the counter starts after the highest id ever handed out.
COLLATE NOCASECOLLATE NOCASEThe same collation.
generated columnsgenerated columns, STORED or VIRTUAL as in the sourceSQLite computes them the same way.
generated columns with functions IntoSQL doesn't write, and the || operatora plain columnA formula that can't be written leaves a plain column and a report line; the dump has no values for generated columns.generated_not_translated
defaults with functions IntoSQL doesn't write (strftime, randomblob)no defaultA default that can't be written is left out and reported; CURRENT_TIMESTAMP and datetime('now') are written.default_not_translated
CHECK constraintstranslatedThe same checks, tested as each row loads.
CHECK constraints with functions IntoSQL doesn't writenot createdA check that can't be written is left out and reported.check_skipped
partial and expression indexesnot createdIntoSQL writes indexes on whole columns; create the others again after loading.index_skipped
foreign keys (unenforced unless PRAGMA foreign_keys=ON)the same foreign keys, listed by PRAGMA foreign_key_checkSQLite checks foreign keys only when told to, so rows without a parent are common: they load, and the key is left unchecked with a warning instead of stopping the load.
foreign keys to tables the dump doesn't createnot createdA key can't name a table that isn't there.foreign_key_skipped
primary keys holding NULL, and NOT NULL columns with values that become NULLNOT NULL droppedSQLite lets a primary key other than the rowid hold NULL; the column can't be NOT NULL in the target, or the load would stop.not_null_relaxed
views, triggers and virtual tables (FTS, R*Tree)listed in the reportTheir code is written in SQLite's own language, and virtual tables need SQLite's modules; they are listed for you to recreate. Their internal tables aren't copied.not_converted_objects
dumps without CREATE TABLE (--data-only)types read from the valuesA data-only dump has only rows, so the types come from the values and the columns are numbered.no_schema
expressions in INSERT values other than the text functions dumps writeNULLunistr(), replace(), char() and || are evaluated as the shell writes them; other expressions can't be without a database.expression_value
NUL characters in textkeptThe sqlite3 shell and Python cut text at a NUL character when they dump it, so only what precedes it is in the dump.
text that isn't valid UTF-8the bad bytes as the replacement characterDumps write such text as raw bytes; the script is UTF-8.invalid_utf8_replaced
text decoded twice (ñ)copied as it isThe dump holds exactly these characters, so they are copied; the report points them out.mojibake_suspected

Load the script into SQLite

The script creates the tables, copies the rows, then adds keys, indexes and foreign keys, so it loads fast and stops at the first error.

sqlite3 -bail mydb.sqlite < sqlite.sqlite.sql

Foreign keys are declared in each table and the rows load with foreign key checks off. The script then lists any row that points to a row that doesn't exist, instead of failing half way.

For a large dump, load into a new file on a local disk. The script loads everything in one transaction with syncing off, so it runs at disk speed, and a load that stops half way leaves no partial tables.

The command line loads the file straight into a SQLite database, without writing a script. It's free.

Dump SQLite for the best result

The converter reads what the dump holds. These options keep more of your data in it:

  • The database file itself converts as it is, read page by page in your browser. Close the app that writes to it first, so no change is left behind in its -wal file.
  • sqlite3 app.db .dump > app.sql writes the same database as SQL, which converts too, as .sql or .sql.gz.
  • DB Browser for SQLite writes that SQL with File, Export, Database to SQL file, and so does Python's iterdump().

What isn't converted

Views, triggers, procedures, functions and other objects written in SQLite's own language are listed in the report by name, never dropped silently, so you can check each one against the SQLite version you load into before recreating it.

Questions about moving SQLite between versions

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.

Can I convert the .db file itself?

Yes. Drop the .db, .sqlite or .sqlite3 file: it is read in your browser and never uploaded. Text with NUL characters and every digit of a double arrive as stored, which a .dump can cut short.

Which SQLite versions can the dump come from?

Database files from SQLite 3.0 on, in UTF-8 or UTF-16, and dumps from the sqlite3 shell's .dump or Python's iterdump(). Every test file and dump comes from sqlite3 3.31 to 3.54, STRICT and WITHOUT ROWID tables included, and is compared with its source cell by cell.

How are column types decided when SQLite doesn't enforce them?

Each column keeps its declared type, and so do COLLATE NOCASE and RTRIM. STRICT is left out, so the script loads on every version from 3.31.

Are INTEGER PRIMARY KEY and AUTOINCREMENT kept?

Yes. INTEGER PRIMARY KEY and AUTOINCREMENT stay as they are, and sqlite_sequence continues where the dump left it.

What about views and triggers?

They are listed in the report by name and not converted, so you can recreate them once the tables are in.

Which SQLite versions does the script load on?

SQLite 3.31 and later. Every test file and dump loads on 3.31 and 3.54, stopping at the first error if anything goes wrong.