Muistiinpano
Tämän sivun käyttö edellyttää valtuutusta. Voit yrittää kirjautua sisään tai vaihtaa hakemistoa.
Tämän sivun käyttö edellyttää valtuutusta. Voit yrittää vaihtaa hakemistoa.
SQLite is the default database for many Python projects, FastAPI, and Flask apps. It lets you build your application quickly by delaying the data platform decision until later. When it's time to take your application to production, you need support for concurrent users, role-based security, high availability, disaster recovery, and other enterprise features. You need to migrate to Microsoft SQL using the mssql-python driver.
SQL dialect differences
When you migrate from SQLite to Microsoft SQL, you need to address two things: rewrite your SQL statements for Transact-SQL (T-SQL) and migrate your data.
The following table maps common SQLite patterns to their Microsoft SQL equivalents:
| SQLite | SQL Server (T-SQL) | Notes |
|---|---|---|
INTEGER PRIMARY KEY AUTOINCREMENT |
int IDENTITY(1,1) PRIMARY KEY |
Microsoft SQL uses IDENTITY for autoincrement. |
TEXT |
nvarchar(255) or nvarchar(max) |
Always specify a length. Use nvarchar for Unicode. |
REAL |
float or decimal(18,2) |
Use decimal for exact values like money. |
BLOB |
varbinary(max) |
Same behavior, different name. |
BOOLEAN (stored as INTEGER) |
bit |
Neither database has a native boolean. Both store 0/1. |
DATETIME('now') |
GETDATE() or SYSDATETIME() |
SYSDATETIME() gives higher precision. |
LIMIT 10 OFFSET 20 |
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY |
Requires an ORDER BY clause. |
\|\| (string concat) |
+ or CONCAT() |
CONCAT() handles NULL values. |
IFNULL(a, b) |
ISNULL(a, b) or COALESCE(a, b) |
COALESCE is ANSI-standard. |
GROUP_CONCAT(col) |
STRING_AGG(col, ',') |
Available in SQL Server 2017+. |
INSERT OR REPLACE INTO |
MERGE statement |
SQLite deletes and reinserts; MERGE updates in place. See example that follows. |
last_insert_rowid() |
OUTPUT INSERTED.id |
Use OUTPUT in the INSERT statement. SCOPE_IDENTITY() also works but requires a separate SELECT. |
typeof(x) |
SQL_VARIANT_PROPERTY(x, 'BaseType') |
Rarely needed with strict typing. |
CREATE TABLE example
-- SQLite
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL DEFAULT 0.0,
created_at TEXT DEFAULT (datetime('now')),
is_active BOOLEAN DEFAULT 1
);
-- SQL Server
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'products')
CREATE TABLE products (
id int IDENTITY(1,1) PRIMARY KEY,
name nvarchar(100) NOT NULL,
price decimal(10,2) DEFAULT 0.0,
created_at datetime2 DEFAULT SYSDATETIME(),
is_active bit DEFAULT 1
);
Query examples
Pagination:
SQLite:
cursor.execute("SELECT * FROM products ORDER BY name LIMIT ? OFFSET ?", (10, 20))
mssql-python:
cursor.execute(
"SELECT * FROM Production.Product ORDER BY Name OFFSET ? ROWS FETCH NEXT ? ROWS ONLY",
(20, 10)
)
The parameter order is reversed. Transact-SQL puts OFFSET before FETCH NEXT.
Upsert (insert or update):
SQLite:
cursor.execute("""
INSERT OR REPLACE INTO settings (key, value)
VALUES (?, ?)
""", (key, value))
mssql-python:
cursor.execute("""
MERGE #Settings AS target
USING (SELECT ? AS [key], ? AS value) AS source
ON target.[key] = source.[key]
WHEN MATCHED THEN UPDATE SET value = source.value
WHEN NOT MATCHED THEN INSERT ([key], value) VALUES (source.[key], source.value);
""", (key, value))
The USING clause defines source.[key] and source.value as column aliases. The WHEN clauses reference those aliases. Only two ? markers are needed.
Last inserted ID:
SQLite:
cursor.execute("INSERT INTO products (name) VALUES (?)", ("Widget",))
product_id = cursor.lastrowid
mssql-python:
cursor.execute(
"INSERT INTO #Products (Name) OUTPUT INSERTED.ProductID VALUES (?)",
("Widget",)
)
product_id = cursor.fetchval()
Update connection code
Replace sqlite3.connect() with mssql_python.connect():
SQLite:
import sqlite3
def get_connection():
conn = sqlite3.connect("myapp.db")
conn.row_factory = sqlite3.Row
return conn
mssql-python:
import mssql_python
def get_connection():
return mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes;"
)
Row access works similarly. SQLite's Row factory returns dict-like rows with row["column"] syntax. The mssql-python driver returns Row objects that support the same string-key access, plus attribute and index access:
SQLite (with row_factory):
row["name"]
mssql-python:
row["name"] # String-key access, like SQLite
row.name # Attribute access
row[0] # Index access
Update parameter style
Both SQLite and mssql-python use ? as the parameter marker, so most queries work without changes.
# Works with both sqlite3 and mssql-python
cursor.execute("SELECT * FROM Production.Product WHERE Name = ?", ("Adjustable Race",))
The one difference: SQLite allows named parameters with :name syntax. The mssql-python driver uses %(name)s instead.
SQLite:
cursor.execute("SELECT * FROM products WHERE id = :id", {"id": 42})
mssql-python:
cursor.execute("SELECT * FROM Production.Product WHERE ProductID = %(id)s", {"id": 42})
Migrate existing data
Important considerations when migrating data from SQLite to Microsoft SQL:
- Microsoft SQL stores nvarchar(max) and varbinary(max) values as large objects (LOBs), which are slower to read and write than in-row data. Keep string columns at nvarchar(4000) or less whenever your data allows. SQL Server stores these values directly in the data row, avoiding the LOB overhead.
- The type mapping follows SQLite's type affinity rules. Review the generated tables after migration to tighten column sizes (for example, nvarchar(100) instead of nvarchar(4000)) or add constraints that SQLite didn't enforce.
- For SQLite tables that use TEXT to store dates, you might need to parse the values into Python
datetimeobjects before inserting. Microsoft SQL expects proper datetime values, not text strings.
Use this script to read the schema and data from SQLite and create matching tables in Microsoft SQL:
import sqlite3
import mssql_python
# SQLite type affinity -> Microsoft SQL type
# Keywords follow SQLite's type affinity rules
TYPE_MAP = {
"INT": "bigint",
"CHAR": "nvarchar(4000)",
"CLOB": "nvarchar(max)",
"TEXT": "nvarchar(4000)",
"BLOB": "varbinary(max)",
"REAL": "float",
"FLOA": "float",
"DOUB": "float",
}
def map_type(sqlite_type: str) -> str:
"""Map a SQLite column type to a Microsoft SQL type."""
upper = (sqlite_type or "TEXT").upper()
for prefix, sql_type in TYPE_MAP.items():
if prefix in upper:
return sql_type
return "decimal(18,6)" # NUMERIC affinity (default)
# Connect to both databases
sqlite_conn = sqlite3.connect("myapp.db")
sql_conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes;"
)
# Get list of tables from SQLite
sqlite_cur = sqlite_conn.cursor()
sqlite_cur.execute(
"SELECT name FROM sqlite_master "
"WHERE type='table' AND name NOT LIKE 'sqlite_%'"
)
tables = [row[0] for row in sqlite_cur.fetchall()]
sql_cursor = sql_conn.cursor()
for table in tables:
# Read column info from SQLite
sqlite_cur.execute(f"PRAGMA table_info([{table}])")
columns = sqlite_cur.fetchall()
# columns: (cid, name, type, notnull, default_value, pk)
# Build CREATE TABLE statement
col_defs = []
for col in columns:
name, col_type, notnull, pk = col[1], col[2], col[3], col[5]
sql_type = map_type(col_type)
parts = [f"[{name}] {sql_type}"]
if notnull:
parts.append("NOT NULL")
if pk:
parts.append("PRIMARY KEY")
col_defs.append(" ".join(parts))
create_sql = f"CREATE TABLE [{table}] ({', '.join(col_defs)})"
sql_cursor.execute(
f"IF OBJECT_ID('{table}','U') IS NULL " + create_sql
)
sql_conn.commit()
# Read all rows from SQLite
sqlite_cur.execute(f"SELECT * FROM [{table}]")
rows = sqlite_cur.fetchall()
if not rows:
print(f" {table}: created (empty)")
continue
# Use bulkcopy for fast insert
result = sql_cursor.bulkcopy(table, rows)
print(f" {table}: {result['rows_copied']} rows copied")
sql_conn.commit()
sqlite_conn.close()
sql_conn.close()
Feature differences
After migration, your application gains access to Microsoft SQL features that SQLite doesn't support:
| Feature | SQLite | SQL Server |
|---|---|---|
| Concurrent writes | Single writer at a time | Full concurrency with row-level locking |
| Authentication | File permissions only | SQL auth, Windows auth, Microsoft Entra ID |
| Stored procedures | Not supported | Full T-SQL programmability |
| Encryption | Not built in | TLS in transit, TDE at rest |
| Transactions | Savepoints, basic isolation levels | Full isolation levels, distributed transactions |
| JSON support | json_extract() |
OPENJSON(), JSON_VALUE(), FOR JSON |
| Full-text search | FTS5 extension | Built-in full-text indexing |
| Max database size | ~281 TB (practical limit lower) | 524 PB |
| Connection pooling | N/A (in-process) | Built-in with mssql-python |