mssql-pythonを使ってSQLiteからMicrosoft SQLへ移行する

SQLiteは多くのPythonプロジェクト、FastAPI、Flaskアプリのデフォルトデータベースです。 データプラットフォームの決定を後回しにすることで、アプリケーションを迅速に構築できます。 アプリケーションを本番環境に投入する際には、同時ユーザーのサポート、ロールベースのセキュリティ、高可用性、災害復旧、その他のエンタープライズ機能が必要です。 mssql-pythonドライバーを使ってMicrosoft SQLに移行する必要があります。

SQL方言の違い

SQLiteからMicrosoft SQLに移行する際には、2つのことに対処する必要があります。Transact-SQL 用にSQL文を書き直すこと(T-SQL)とデータの移行です。

以下の表は、一般的なSQLiteパターンをMicrosoftのSQL対応パターンにマッピングしています:

SQLite SQL Server (T-SQL) Notes
INTEGER PRIMARY KEY AUTOINCREMENT int IDENTITY(1,1) PRIMARY KEY Microsoft SQLは自動増分にIDENTITYを使用します。
TEXT nvarchar(255) または nvarchar(max) 必ず長さを指定してください。 Unicodeには nvarchar を使いましょう。
REAL float または decimal(18,2) お金のような正確な価値 decimal 使うのが良いです。
BLOB varbinary(max) 同じ行動で、名前が違う。
BOOLEAN (整数として保存) bit どちらのデータベースにもネイティブのブール値は存在しません。 どちらも 0/1を保存しています。
DATETIME('now') GETDATE() または SYSDATETIME() SYSDATETIME() より高い精度をもたらします。
LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY ORDER BY条項が必要です。
\|\|(文字列の連結) + または CONCAT() CONCAT() NULL値を扱います。
IFNULL(a, b) ISNULL(a, b) または COALESCE(a, b) COALESCE ANSI標準です。
GROUP_CONCAT(col) STRING_AGG(col, ',') SQL Server 2017+で利用可能。
INSERT OR REPLACE INTO MERGE ステートメント SQLiteは削除と再挿入を行います。 MERGE アップデートが実施されています。 以下の例を参照してください。
last_insert_rowid() OUTPUT INSERTED.id OUTPUT文でINSERTを使いましょう。 SCOPE_IDENTITY() これも使えますが、別の SELECTが必要です。
typeof(x) SQL_VARIANT_PROPERTY(x, 'BaseType') 厳密なタイプ設定ではほとんど必要ありません。

CREATE TABLE 例

-- 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
);

クエリの例

ページ数:

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)
)

パラメータの順序が逆になっています。 Transact-SQL OFFSETFETCH NEXTより優先します。

アップサート(挿入または更新):

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))

USING節はsource.[key]source.valueを列のエイリアスとして定義しています。 WHEN条項はこれらの別名を参照しています。 必要な ? 標は2つだけです。

最終入力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()

接続コードの更新

sqlite3.connect()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;"
    )

行アクセスも同様に機能します。 SQLiteのRowファクトリは、row["column"]構文でアクセスできる辞書のような行を返します。 mssql-pythonドライバは、同じ文字列キーアクセスに加え、属性およびインデックスアクセスをサポートする Row オブジェクトを返します。

SQLite(row_factory付き):

row["name"]

mssql-python:

row["name"]   # String-key access, like SQLite
row.name      # Attribute access
row[0]        # Index access

パラメータスタイルを更新

SQLiteもmssql-pythonもパラメータマーカーとして ? を使用しているため、ほとんどのクエリは変更なしで動作します。

# Works with both sqlite3 and mssql-python
cursor.execute("SELECT * FROM Production.Product WHERE Name = ?", ("Adjustable Race",))

唯一の違いは、SQLiteが命名済みパラメータを使えば :name 構文を使えることです。 mssql-pythonのドライバーは代わりに %(name)s を使用します。

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})

既存データの移行

SQLiteからMicrosoft SQLへのデータ移行における重要な考慮事項:

  • Microsoft SQLはnvarchar(max)およびvarbinary(max)の値を大きなオブジェクト(LOB)として保存しており、行内データよりも読み書きが遅いです。 データが許す限り、文字列の列は nvarchar(4000) 以下に保ちましょう。 SQL Serverはこれらの値を直接データ行に格納し、LOBのオーバーヘッドを回避します。
  • 型マッピングはSQLiteの 型親和性ルールに従っています。 移行後に生成されたテーブルを確認し、列サイズを厳しく調整したり(例:nvarchar(4000)ではなくnvarchar(100)に設定したり)、SQLiteで強制されなかった制約を追加したりしましょう。
  • SQLite のテーブルで日付の保存に TEXT を使用する場合、挿入する前に値を Python datetime オブジェクトに解析する必要があるかもしれません。 Microsoft SQLはテキスト文字列ではなく、適切なdatetime値を期待しています。

このスクリプトを使ってSQLiteのスキーマとデータを読み取り、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()

機能の違い

移行後、あなたのアプリケーションはSQLiteがサポートしないMicrosoft SQL機能にアクセスできます:

特徴 SQLite SQL Server
同時書き込み 一度に一人の作家を 行レベルロックによる完全並行処理
Authentication ファイル権限のみ SQL 認証、Windows 認証、Microsoft Entra ID
ストアド プロシージャ サポートしていません 完全なT-SQLプログラマビリティ
Encryption 内蔵ではありません TLSは移動中、TDEは静止中
Transactions セーブポイント、基本的な隔離レベル 完全な隔離レベル、分散トランザクション
JSON のサポート json_extract() OPENJSON()JSON_VALUE()FOR JSON
フルテキスト検索 FTS5拡張 組み込みの全文インデックス作成
最大データベース サイズ ~281TB(実用的な上限下) 524 ペタバイト (PB)
コネクションプーリング N/A(進行中) mssql-pythonに組み込み