Skip to content
IntoSQL home

Migration guides

SQLite to SQL Server migration guide

Convert a SQLite database or dump into a SQL Server script with the same tables, keys, indexes and data: how to dump, convert, load, and every rule the converter applies.

View as Markdown

IntoSQL reads a SQLite database file, or the SQL that SQLite's .dump writes, and turns it into one SQL Server script with the same tables, columns, keys, indexes, foreign keys, defaults, counters and rows. SQLite keeps any value in any column, so the declared type is a hint: each column gets the SQL Server type all of its values fit, and a column holding mixed values keeps their text. Every value SQL Server can't hold as it is, and every object it doesn't carry, is listed in the report. The script loads on SQL Server 2017 and later.

Convert in the browser: SQLite to SQL Server opens the converter set up for this pair.

The file or a dump

Drop the database file itself (.db, .sqlite, .sqlite3): it is read page by page, in UTF-8 or UTF-16, and text with NUL characters and every digit of a double arrive as stored. Close the app that writes to it first, so no change is left behind in its -wal file.

A dump converts too, as .sql or .sql.gz:

Terminal
sqlite3 shop.db .dump > shop.sql
  • DB Browser for SQLite writes the same SQL with File, Export, Database to SQL file, and so does Python's iterdump().
  • .dump --nosys converts too. A --data-only dump has no CREATE TABLE, so its columns are named column_1, column_2 and so on, and the types come from the values.
  • Every test file and dump comes from sqlite3 3.31 to 3.54, with STRICT and WITHOUT ROWID tables, and is compared with its source cell by cell.

Convert

In the browser, open SQLite to SQL Server or the converter on the home page, drop the file and choose SQL Server. It reads the file from your disk in pieces, twice, and writes the script as it goes, so the file is never uploaded and its size isn't limited by memory. The desktop app does the same with the file on disk.

With the command line:

Terminal
intosql inspect shop.db
intosql convert shop.db --to sql-server --output shop.sql_server.sql --report shop.report.json

inspect lists the tables, their rows and what won't be converted, without writing anything. convert writes the script and, with --report, the report as JSON.

Through the API, a database file or a dump converts like any other file: open a conversion with its name and "output": {"dialect": "sql-server"}.

Load into SQL Server

Terminal
sqlcmd -S localhost -Q "CREATE DATABASE shop"
sqlcmd -b -f 65001 -S localhost -d shop -i shop.sql_server.sql

-b stops at the first error and -f 65001 reads the script as UTF-8. Text columns compare exactly, as in SQLite (Latin1_General_100_BIN2), and COLLATE NOCASE columns ignore case (Latin1_General_100_CI_AS_SC), so unique keys keep the same rows apart. Text in a key is nvarchar(450) when its values fit, since SQL Server can't index nvarchar(max). The script creates the tables, copies the rows in batches of up to 1000 with IDENTITY_INSERT, then adds indexes, foreign keys and identities. Foreign keys are added WITH NOCHECK and then checked: a row that points to a missing row is named by its constraint instead of failing the load half way.

What isn't converted

Views and triggers are written in SQLite's own language. They are listed in the report by kind and name, never dropped silently, so you can recreate them in SQL Server. Virtual tables, such as FTS5 and R*Tree, are listed too, and their shadow tables are left out.

Data types

SQLite to SQL Server data types lists each type that changes in the script: what it becomes, why, and the report line.

Every other rule

Generated from the engine, where each rule is decided.

In SQLite In SQL Server Why Report line
COLLATE NOCASE a case-insensitive collation SQL Server's usual collation ignores case for every column, as NOCASE did for ASCII letters. None
generated columns computed columns, PERSISTED when STORED SQL Server computes them the same way. None
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. 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. default_not_translated
CHECK constraints translated The same checks, tested as each row loads. None
CHECK constraints with functions IntoSQL doesn't write not created A check that can't be written is left out and reported. check_skipped
partial and expression indexes not created IntoSQL writes indexes on whole columns; create the others again after loading. index_skipped
foreign keys (unenforced unless PRAGMA foreign_keys=ON) after the data WITH NOCHECK, then checked 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. None
ON DELETE or ON UPDATE that cascades in a cycle or along a second path NO ACTION SQL Server refuses cascades that could reach a table twice. foreign_key_action_dropped
foreign keys to tables the dump doesn't create not created A 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 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. 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. 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. 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. expression_value
text decoded twice (ñ) copied as it is The dump holds exactly these characters, so they are copied; the report points them out. mojibake_suspected