Skip to content
IntoSQL home

Convert SQL Server to SQLite

Drop the script SSMS's Generate Scripts writes with schema and data, as .sql or .sql.gz, UTF-16 or UTF-8, and download a script for SQLite with the same tables, keys, indexes and data. Every value SQLite can't hold as it is, and every object it doesn't carry, is listed in the report.

Your file
sqlshop_2024_sqlserver.sqlSample file, 11 kB
Your database

18 rows converted, 3 fixes, none rejected

Show

Tables in shop_2024_sqlserver.sqlSQL Server dump · written by SQL Server Management Objects · utf-8

  • orders10 rows2
    11 columns
    idINTEGER not null
    customer_idINTEGER not null
    statusVARCHAR(10) not null
    totalTEXT not null
    itemsINTEGER not null
    ordered_atDATETIME not null
    shipped_atDATETIME
    noteTEXT
    detailsTEXT
    order_refTEXT not null
    total_with_taxDECIMAL(12,2)
  • customers8 rows1
    8 columns
    idINTEGER not null
    nameVARCHAR(100) not null
    emailVARCHAR(200) not null
    countryVARCHAR(2) not null
    is_activeINTEGER not null
    signed_upDATE
    created_atTIMESTAMP not null
    row_versionBLOB

Not converted

  • View paid_orders
  • Trigger orders_no_negative

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

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

CREATE TABLE orders (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    customer_id INTEGER NOT NULL,
    status VARCHAR(10) DEFAULT 'pending' NOT NULL,
    total TEXT NOT NULL,
    items INTEGER DEFAULT 1 NOT NULL,
    ordered_at DATETIME NOT NULL,
    shipped_at DATETIME,
    note TEXT,
    details TEXT,
    order_ref TEXT NOT NULL,
    total_with_tax DECIMAL(12,2) GENERATED ALWAYS AS (CAST(total * (1.16) AS NUMERIC)) STORED,
    CONSTRAINT orders_items_check CHECK (items >= 0),
    CONSTRAINT orders_check2 CHECK (status = 'cancelled' OR status = 'shipped' OR status = 'paid' OR status = 'pending'),
    CONSTRAINT orders_check3 CHECK (total >= (0)),
    CONSTRAINT orders_fkey FOREIGN KEY (customer_id) REFERENCES customers (id)
);

CREATE TABLE customers ( -- People who bought at least once
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(200) NOT NULL,
    country VARCHAR(2) DEFAULT 'US' NOT NULL,
    is_active INTEGER DEFAULT 1 NOT NULL,
    signed_up DATE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
    row_version BLOB,
    CONSTRAINT customers_is_active_check CHECK (is_active IN (0, 1))
);

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.0000000');
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.0000000');
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.0000000');
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.0000000');
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.0000000');
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.0000000');
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.0000000');
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.0000000');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(1,1,'shipped','129.9000',3,'2024-03-01T10:00:00.0000000','2024-03-02T16:30:00.1234567',NULL,'{"gift": false}','0b7a4c2e-3f1d-4e8a-9c55-1a2b3c4d5e6f');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(2,2,'paid','54.0000',1,'2024-03-02T11:15:00.0000000',NULL,'Gift wrap 🎁','{"gift": true, "wrap": "red"}','1c8b5d3f-4a2e-4f9b-8d66-2b3c4d5e6f70');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(3,1,'cancelled','12.5000',1,'2024-03-03T09:45:00.0000000',NULL,'Customer changed their mind',NULL,'2d9c6e4a-5b3f-4a0c-9e77-3c4d5e6f7081');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(4,4,'shipped','980.0000',12,'2024-03-05T02:10:00.0000000','2024-03-07T08:00:00.0000000','请小心轻放','{"fragile": true}','3e0d7f5b-6c4a-4b1d-8f88-4d5e6f708192');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(5,5,'pending','45.7500',2,'2024-03-08T17:20:00.0000000',NULL,'Leave at the door; ring twice',NULL,'4f1e806c-7d5b-4c2e-9a99-5e6f708192a3');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(6,6,'shipped','310.4000',5,'2024-03-10T13:00:00.0000000','2024-03-11T09:00:00.0000000','Fragile: "glass"','{"fragile": true}','50f2917d-8e6c-4d3f-8baa-6f708192a3b4');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(7,8,'paid','76.2000',2,'2024-03-11T18:45:00.0000000',NULL,NULL,NULL,'6103a28e-9f7d-4e40-9cbb-708192a3b4c5');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(8,2,'shipped','19.9900',1,'2024-03-12T08:00:00.0000000','9999-12-31T23:59:59.9999999','Shipped date set to the maximum by an old import',NULL,'7214b39f-a08e-4f51-8dcc-8192a3b4c5d6');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(9,3,'shipped','64.0000',2,'2024-03-14T15:30:00.0000000','2024-03-15T10:00:00.0000000','Two parcels','{"parcels": [1, 2]}','8325c4a0-b19f-4062-9edd-92a3b4c5d6e7');
INSERT INTO orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) VALUES
(10,7,'paid','1499.0000',1,'2024-03-15T21:05:00.0000000',NULL,'C:\Users\ravi\invoice.pdf',NULL,'9436d5b1-c2a0-4173-8fee-a3b4c5d6e7f8');

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

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

Fixes

  1. "items" of orders is unsigned, so it became INTEGER with a check that it isn't negative.
  2. "total" of orders is money, more digits than a number in SQLite keeps, so it is text with every digit.
  3. "row_version" of customers is a row version SQL Server sets by itself; the dump has no values for it, so it is BLOB and empty.

Needs a look

  • 6 text columns compare ignoring case in the source; SQLite compares exactly, so unique keys and searches may behave differently. Use a case-insensitive collation or column type on the columns that need it.
  • 2 views, triggers, routines or events aren't converted; the report lists each one. Recreate them by hand in SQLite.
  • The check check1 on orders can't be written for SQLite (function isjson), so it isn't created. Write the check by hand if it matters.
  • The index idx_orders_pending on orders uses an expression that can't be written for SQLite (filtered index), so it isn't created. Create the index by hand if queries need it.
  • The default of "order_ref" in orders can't be written for SQLite (function uuid), so the column has no default. Add the default by hand if new rows need it.

Your file is converted on this computer and never uploaded.

Other samples:

What changes from SQL Server to SQLite

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

  • bit

    INTEGER 0 or 1 with a CHECK

    SQLite has no boolean type; its own true and false are 1 and 0.

  • tinyint

    INTEGER with a CHECK that it isn't negative

    SQL Server's tinyint runs from 0 to 255; a check keeps its range.

    Report line: unsigned_widened

  • char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), text

    VARCHAR(n) and TEXT, not enforced

    SQLite keeps the declared type for tools to read but doesn't limit the length.

  • decimal(p,s) past 15 digits, money

    TEXT

    SQLite keeps about 15 significant digits in a number. Wider numbers are kept as text so every digit stays exact.

    Report line: decimal_as_text

  • date, datetime, datetime2, smalldatetime

    DATETIME as ISO 8601 text, every digit kept

    SQLite stores dates as text its date functions read.

  • datetimeoffset

    TIMESTAMP as ISO 8601 text in UTC

    SQLite has no type that keeps an offset, so each moment is written in UTC: the same instant whatever offset it had.

    Report line: moments_as_utc

  • time

    TIME as text

    SQLite keeps times as text, with every digit.

  • dates that aren't valid

    NULL

    A value that isn't a date becomes NULL and the report counts it.

    Report line: date_out_of_range

  • json (SQL Server 2025)

    TEXT

    The same JSON values, which SQLite's JSON functions read.

  • uniqueidentifier

    TEXT

    The same identifiers, written in lowercase.

  • binary, varbinary, image

    BLOB

    Bytes stay bytes.

  • geometry, geography

    BLOB with SQL Server's own bytes

    SSMS writes spatial values in SQL Server's serialization, which is kept byte for byte.

    Report line: spatial_as_native

  • rowversion, timestamp

    BLOB, empty

    SQL Server sets a row version by itself and SSMS scripts no values for it.

    Report line: rowversion_empty

  • xml, sql_variant, hierarchyid, vector

    TEXT

    SQLite has no equivalent type; xml and sql_variant keep each value's text and hierarchyid its path, such as /1/2/.

    Report line: type_replaced

  • collations that ignore case (_CI_)

    exact comparison

    SQLite compares text exactly unless told otherwise, and its NOCASE only folds ASCII letters.

    Report line: case_insensitive_collation

  • IDENTITY

    INTEGER PRIMARY KEY AUTOINCREMENT, continuing after the highest id

    SQLite counts a whole integer primary key, and AUTOINCREMENT keeps ids from being reused.

  • IDENTITY columns that aren't the whole primary key

    INTEGER without a counter

    SQLite only counts a column that is the whole primary key, so the column keeps its values and new rows need theirs.

    Report line: counter_dropped

  • CREATE SEQUENCE

    not created

    SQLite has no sequences. The values a sequence handed out are in the rows.

    Report line: sequence_not_converted

  • computed columns

    GENERATED ALWAYS AS, STORED when PERSISTED, VIRTUAL otherwise

    SQLite 3.31 and later compute them the same two ways, with CONCAT written so NULL counts as empty as in SQL Server.

  • computed columns with functions SQLite lacks

    a plain column

    A formula that can't be written leaves a plain column and a report line; SSMS scripts no values for computed columns.

    Report line: generated_not_translated

  • defaults with functions SQLite lacks, and NEXT VALUE FOR

    no default

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

    Report line: default_not_translated

  • CHECK constraints

    translated

    The same checks, tested as each row loads.

  • CHECK constraints with functions SQLite lacks

    not created

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

    Report line: check_skipped

  • CHECK constraints added WITH NOCHECK or disabled

    not created

    SQL Server doesn't apply these checks, so the rows may break them.

    Report line: check_not_enforced

  • filtered and columnstore indexes

    not created

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

    Report line: index_skipped

  • foreign keys

    in CREATE TABLE, loaded unchecked, then listed by PRAGMA foreign_key_check

    SQLite takes foreign keys only in CREATE TABLE and checks them only with PRAGMA foreign_keys on. The rows load unchecked and the script ends listing any row without a parent.

  • 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

  • tables on a partition scheme

    one table with every partition's rows

    The rows are the same; partition the table again in SQLite if it needs it.

    Report line: partitions_dropped

  • temporal tables (SYSTEM_VERSIONING)

    two tables: the current rows and the history

    The table and its history table are copied as plain tables; SQLite doesn't keep the history by itself.

    Report line: system_versioning_dropped

  • schemas

    one file, tables named after their schema first

    A SQLite file is one database, so the schema name keeps tables with the same name apart; tables in dbo keep their names.

    Report line: schemas_merged

  • NOT NULL columns with values that become NULL

    NOT NULL dropped

    A column whose values had to become NULL can't keep NOT NULL, or the load would stop.

    Report line: not_null_relaxed

  • views, functions, procedures, triggers, synonyms, types and partition functions

    listed in the report

    Their code is written in T-SQL, so they are listed for you to recreate.

    Report line: not_converted_objects

  • scripts without CREATE TABLE (data only)

    types read from the values

    A data-only script has only rows, so the types come from the values.

    Report line: no_schema

  • expressions in INSERT values

    NULL

    A converter that doesn't run a database can't evaluate them.

    Report line: expression_value

  • types IntoSQL doesn't know

    TEXT

    Text holds any value; change the type after loading.

    Report line: unknown_type

  • NUL characters in text

    kept

    SQLite text can hold a NUL character, so the value is copied as it is.

  • text that isn't valid in its encoding

    the bad bytes as the replacement character

    The script is UTF-8 and every value has to be valid text.

    Report line: invalid_utf8_replaced

  • text decoded twice (ñ)

    copied as it is

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

    Report line: mojibake_suspected

In SQL ServerIn SQLiteReport line
bitINTEGER 0 or 1 with a CHECKSQLite has no boolean type; its own true and false are 1 and 0.
tinyintINTEGER with a CHECK that it isn't negativeSQL Server's tinyint runs from 0 to 255; a check keeps its range.unsigned_widened
char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), textVARCHAR(n) and TEXT, not enforcedSQLite keeps the declared type for tools to read but doesn't limit the length.
decimal(p,s) past 15 digits, moneyTEXTSQLite keeps about 15 significant digits in a number. Wider numbers are kept as text so every digit stays exact.decimal_as_text
date, datetime, datetime2, smalldatetimeDATETIME as ISO 8601 text, every digit keptSQLite stores dates as text its date functions read.
datetimeoffsetTIMESTAMP as ISO 8601 text in UTCSQLite has no type that keeps an offset, so each moment is written in UTC: the same instant whatever offset it had.moments_as_utc
timeTIME as textSQLite keeps times as text, with every digit.
dates that aren't validNULLA value that isn't a date becomes NULL and the report counts it.date_out_of_range
json (SQL Server 2025)TEXTThe same JSON values, which SQLite's JSON functions read.
uniqueidentifierTEXTThe same identifiers, written in lowercase.
binary, varbinary, imageBLOBBytes stay bytes.
geometry, geographyBLOB with SQL Server's own bytesSSMS writes spatial values in SQL Server's serialization, which is kept byte for byte.spatial_as_native
rowversion, timestampBLOB, emptySQL Server sets a row version by itself and SSMS scripts no values for it.rowversion_empty
xml, sql_variant, hierarchyid, vectorTEXTSQLite has no equivalent type; xml and sql_variant keep each value's text and hierarchyid its path, such as /1/2/.type_replaced
collations that ignore case (_CI_)exact comparisonSQLite compares text exactly unless told otherwise, and its NOCASE only folds ASCII letters.case_insensitive_collation
IDENTITYINTEGER PRIMARY KEY AUTOINCREMENT, continuing after the highest idSQLite counts a whole integer primary key, and AUTOINCREMENT keeps ids from being reused.
IDENTITY columns that aren't the whole primary keyINTEGER without a counterSQLite only counts a column that is the whole primary key, so the column keeps its values and new rows need theirs.counter_dropped
CREATE SEQUENCEnot createdSQLite has no sequences. The values a sequence handed out are in the rows.sequence_not_converted
computed columnsGENERATED ALWAYS AS, STORED when PERSISTED, VIRTUAL otherwiseSQLite 3.31 and later compute them the same two ways, with CONCAT written so NULL counts as empty as in SQL Server.
computed columns with functions SQLite lacksa plain columnA formula that can't be written leaves a plain column and a report line; SSMS scripts no values for computed columns.generated_not_translated
defaults with functions SQLite lacks, and NEXT VALUE FORno defaultA default that can't be written is left out and reported.default_not_translated
CHECK constraintstranslatedThe same checks, tested as each row loads.
CHECK constraints with functions SQLite lacksnot createdA check that can't be written is left out and reported.check_skipped
CHECK constraints added WITH NOCHECK or disablednot createdSQL Server doesn't apply these checks, so the rows may break them.check_not_enforced
filtered and columnstore indexesnot createdIntoSQL writes b-tree indexes on whole columns; create the others again after loading.index_skipped
foreign keysin CREATE TABLE, loaded unchecked, then listed by PRAGMA foreign_key_checkSQLite takes foreign keys only in CREATE TABLE and checks them only with PRAGMA foreign_keys on. The rows load unchecked and the script ends listing any row without a parent.
foreign keys to tables the dump doesn't createnot createdA key can't name a table that isn't there.foreign_key_skipped
tables on a partition schemeone table with every partition's rowsThe rows are the same; partition the table again in SQLite if it needs it.partitions_dropped
temporal tables (SYSTEM_VERSIONING)two tables: the current rows and the historyThe table and its history table are copied as plain tables; SQLite doesn't keep the history by itself.system_versioning_dropped
schemasone file, tables named after their schema firstA SQLite file is one database, so the schema name keeps tables with the same name apart; tables in dbo keep their names.schemas_merged
NOT NULL columns with values that become NULLNOT NULL droppedA column whose values had to become NULL can't keep NOT NULL, or the load would stop.not_null_relaxed
views, functions, procedures, triggers, synonyms, types and partition functionslisted in the reportTheir code is written in T-SQL, so they are listed for you to recreate.not_converted_objects
scripts without CREATE TABLE (data only)types read from the valuesA data-only script has only rows, so the types come from the values.no_schema
expressions in INSERT valuesNULLA converter that doesn't run a database can't evaluate them.expression_value
types IntoSQL doesn't knowTEXTText holds any value; change the type after loading.unknown_type
NUL characters in textkeptSQLite text can hold a NUL character, so the value is copied as it is.
text that isn't valid in its encodingthe bad bytes as the replacement characterThe script is UTF-8 and every value has to be valid text.invalid_utf8_replaced
text decoded twice (ñ)copied as it isThe script 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 < sql_server.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 SQL Server for the best result

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

  • In SSMS, right-click the database, Tasks, Generate Scripts, and pick the tables. dbatools, mssql-scripter and other tools built on the same library write the same script.
  • Under Advanced, set Types of data to script to Schema and data. A script of the schema alone has tables without rows.
  • Save it as Unicode, SSMS's default, or as UTF-8: both are read the same way, and gzip works too.

What isn't converted

Views, functions, procedures, triggers, synonyms, rules and security policies are written in T-SQL. They are listed in the report by name, never dropped silently, so you can rewrite them for SQLite.

Questions about SQL Server to SQLite

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.

Does it read the UTF-16 file SSMS saves?

Yes. SSMS saves scripts as UTF-16 by default, and the converter reads UTF-16 and UTF-8 alike, gzipped or not, with GO between batches.

Which SQL Server versions can the script come from?

Scripts that SSMS, dbatools or mssql-scripter write from SQL Server 2017 on. Every test script comes from SQL Server 2017, 2022 and 2025 and is compared with its source cell by cell.

Are IDENTITY values kept?

An IDENTITY column that is the table's whole primary key becomes INTEGER PRIMARY KEY AUTOINCREMENT, every row keeps its id, and the next id continues after the highest one. Any other keeps its values without a counter.

What happens to money, datetimeoffset and other SQL Server types?

bit becomes 0 and 1, datetime2 ISO 8601 text with every digit, datetimeoffset UTC text, and wide decimals and money exact text. Computed columns stay computed. Schemas become table name prefixes.

What about procedures, triggers and views?

They are listed in the report by name and not converted, because each database writes them in its own language.

Which SQLite versions does the script load on?

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