Bermigrasi dari pyodbc ke mssql-python

Driver mssql-python adalah driver Python pihak pertama Microsoft untuk Microsoft SQL. Jika Anda lebih suka opsi driver yang dikelola Microsoft, ia menawarkan:

  • Tidak ada dependensi driver ODBC eksternal.
  • Pool koneksi bawaan.
  • Dukungan modern untuk Python 3.10+
  • Autentikasi Microsoft Entra asli.

Perbedaan utama

Feature pyodbc mssql-python
Gaya parameter qmark(?) qmark (?) dan pyformat (%(name)s)
Diperlukan driver ODBC Yes Tidak.
Pemanfaatan koneksi Eksternal Built-in
Python minimum 3.6 3.10
callproc() Dukungan Tidak diimplementasikan
Autocommit bawaan Off Off

Langkah-langkah migrasi dasar

Langkah-langkah berikut mencakup perubahan kunci untuk memigrasikan aplikasi pyodbc ke mssql-python.

1. Perbarui impor

Ganti pyodbc impor dengan mssql_python:

Sebelum (pyodbc):

import pyodbc

Setelah (mssql-python):

import mssql_python

2. Perbarui string koneksi

Hapus DRIVER= kata kunci dan perbarui metode autentikasi:

Sebelum (pyodbc, membutuhkan driver ODBC):

conn = pyodbc.connect(
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=localhost;"
    "DATABASE=AdventureWorks2022;"
    "Trusted_Connection=yes;"
)

Setelah (mssql-python, tidak diperlukan driver, menggunakan autentikasi Microsoft Entra):

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes;"
)

3. Jaga agar pertanyaan Anda tetap as-is

Driver mssql-python mendukung kedua gaya parameter ? (qmark) dan %(name)s (pyformat). Kueri Anda yang sudah ada ? tetap berfungsi tanpa perubahan:

Sebelum (pyodbc):

cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))

Setelah (mssql-python, kueri yang sama):

cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))

4. Biarkan executemany apa adanya

Panggilan executemany yang sudah ada dengan tuple dan marker ? tetap berfungsi tanpa perubahan:

Sebelum (pyodbc):

cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)

Setelah (mssql-python, kode yang sama):

cursor.execute("DROP TABLE IF EXISTS #MigrateDemo")
cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)

Migrasi prosedur tersimpan

Driver mssql-python tidak mengimplementasikan callproc(). Bagian berikut menjelaskan cara menggunakan EXECUTE sebagai gantinya.

Gunakan EXECUTE untuk prosedur tersimpan

Driver pyodbc mendukung callproc(), tetapi driver mssql-python tidak. Gunakan EXECUTE sebagai gantinya:

Sebelum (pyodbc):

cursor.callproc("dbo.uspGetEmployeeManagers", (5,))
results = cursor.fetchall()

Setelah (mssql-python):

cursor.execute(
    "EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = %(id)s",
    {"id": 5}
)
results = cursor.fetchall()
print(f"Got {len(results)} rows")

Parameter output

Gunakan variabel T-SQL untuk menangkap nilai output alih-alih mengandalkan callproc() parameter output:

Sebelum (pyodbc, menggunakan callproc):

params = (category_id, pyodbc.SQL_INTEGER)
cursor.callproc("dbo.GetProductCount", params)
count = params[1].value

Setelah (mssql-python, menggunakan variabel T-SQL):

cursor.execute(
    """
    DECLARE @count INT;
    SELECT @count = COUNT(*) FROM Production.Product
    WHERE ProductSubcategoryID = %(cat_id)s;
    SELECT @count AS ProductCount;
    """,
    {"cat_id": 1}
)
product_count = cursor.fetchval()
print(f"Product count: {product_count}")

Migrasi khusus fitur

Bagian berikut mencakup fitur pyodbc tertentu dan padanan mssql-pythonnya.

Rangkaian koneksi

Kata kunci pyodbc Kata kunci mssql-python Catatan
DRIVER={...} Tidak diperlukan Driver ODBC telah disertakan secara internal.
SERVER= Server= Tidak ada perubahan perilaku.
DATABASE= Database= Tidak ada perubahan perilaku.
Trusted_Connection= Trusted_Connection= Tidak ada perubahan perilaku.
UID= / PWD= UID= / PWD= Tidak ada perubahan perilaku.
Authentication= Authentication= Menerima nilai yang sama.

Komit otomatis

Perilaku autocommit identik pada kedua driver:

Pyodbc:

conn.autocommit = True
pyodbc.connect(connection_string, autocommit=True)

mssql-python:

conn.autocommit = True

Sisipan massal

Untuk mempercepat batch besar INSERT , pengguna pyodbc mengatur fast_executemany = True. Driver mssql-python sudah mengoptimalkan executemany untuk batch berparameter, sehingga penyisipan dalam jumlah sedang tidak memerlukan flag khusus. Untuk pemuatan data besar, lebih suka bulkcopy(), yang mengalirkan baris melalui protokol salinan massal dan jauh lebih cepat daripada menerbitkan pernyataan individual INSERT . Untuk alur kerja lengkap, lihat Menggunakan salinan massal.

Pyodbc:

cursor.fast_executemany = True
cursor.executemany(query, data)

Setelah (mssql-python), moderasi batch dengan executemany:

cursor.execute("DROP TABLE IF EXISTS #BulkTarget")
cursor.execute("CREATE TABLE #BulkTarget (ID INT, Name NVARCHAR(50))")
data = [(i, f"Item {i}") for i in range(100)]
cursor.executemany("INSERT INTO #BulkTarget (ID, Name) VALUES (?, ?)", data)
conn.commit()

Setelah (mssql-python), muatan besar dengan bulkcopy (lebih disukai):

cursor.execute("IF OBJECT_ID('##BulkTarget') IS NOT NULL DROP TABLE ##BulkTarget")
cursor.execute("CREATE TABLE ##BulkTarget (ID INT, Name NVARCHAR(50))")
conn.commit()  # Commit DDL before bulkcopy
data = [(i, f"Item {i}") for i in range(100)]
result = cursor.bulkcopy("##BulkTarget", data)
print(f"Bulk copied {result['rows_copied']} rows")
cursor.execute("DROP TABLE ##BulkTarget")
conn.commit()

Pabrik baris

driver mssql-python mengembalikan objek Row yang mendukung akses atribut secara bawaan, tanpa memerlukan row factory kustom:

Pyodbc (Pabrik Baris Kustom):

def namedtuple_row_factory(cursor):
    from collections import namedtuple
    columns = [col[0] for col in cursor.description]
    Row = namedtuple("Row", columns)
    return Row

mssql-python (akses atribut secara default):

cursor.execute("SELECT Name, ListPrice FROM Production.Product")
row = cursor.fetchone()
print(row.Name)   # Attribute access works directly
print(row[0])     # Index access also works

Penanganan kesalahan

Driver mssql-python menggunakan hierarki pengecualian yang sama dengan pyodbc, sehingga sebagian besar penangan pengecualian hanya memerlukan perubahan nama modul.

Hierarki pengecualian

Nama kelas pengecualian dipetakan langsung di antara driver:

Pyodbc:

try:
    cursor.execute(query)
except pyodbc.Error as e:
    pass
except pyodbc.DatabaseError as e:
    pass
except pyodbc.OperationalError as e:
    pass

mssql-python:

try:
    cursor.execute("SELECT TOP 1 * FROM Production.Product")
    print(cursor.fetchone())
except mssql_python.Error as e:
    pass
except mssql_python.DatabaseError as e:
    pass
except mssql_python.OperationalError as e:
    pass

Rincian kesalahan

Kedua driver mengekspos detail kesalahan melalui argumen pengecualian:

Pyodbc:

try:
    cursor.execute(query)
except pyodbc.Error as e:
    sqlstate = e.args[0]
    message = e.args[1]

mssql-python:

try:
    cursor.execute("SELECT TOP 1 * FROM NonExistentTable_XYZ")
except mssql_python.Error as e:
    # Error message contains SQLSTATE and details
    print(str(e))

Pemanfaatan koneksi

Driver mssql-python menyertakan pengumpulan koneksi secara default, sehingga pustaka pengumpulan eksternal tidak lagi diperlukan.

Hapus pengumpulan eksternal

Jika Anda menggunakan pengumpulan eksternal dengan pyodbc, driver mssql-python memilikinya:

Sebelum (kolam eksternal pyodbc):

from dbutils.pooled_db import PooledDB

pool = PooledDB(pyodbc, 5, driver="{ODBC Driver 18 for SQL Server}",
                server="your_server", database="your_database",
                uid="your_username", pwd="your_password")
conn = pool.connection()

Setelah (pooling bawaan mssql-python):

conn = mssql_python.connect(connection_string)
conn.close()

Mengonfigurasi kumpulan

Timpa ukuran pool dan batas waktu default dengan mssql_python.pooling():

import mssql_python

mssql_python.pooling()

Contoh migrasi lengkap

Berikut ini menunjukkan fungsi yang sama yang ditulis dengan pyodbc dan kemudian ditulis ulang dengan mssql-python.

Sebelum (pyodbc)

Versi ini menggunakan string koneksi pyodbc dengan DRIVER kata kunci:

import pyodbc
from datetime import date

def get_orders(customer_id: int, start_date: date):
    conn = pyodbc.connect(
        "DRIVER={ODBC Driver 18 for SQL Server};"
        "SERVER=localhost;"
        "DATABASE=AdventureWorks2022;"
        "Trusted_Connection=yes;"
    )
    cursor = conn.cursor()

    cursor.execute("""
        SELECT SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = ? AND OrderDate >= ?
        ORDER BY OrderDate DESC
    """, (customer_id, start_date))

    orders = []
    for row in cursor:
        orders.append({
            "id": row.SalesOrderID,
            "date": row.OrderDate,
            "total": row.TotalDue
        })

    cursor.close()
    conn.close()
    return orders

Setelah (mssql-python)

Versi ini menghapus DRIVER kata kunci. Semua kueri, parameter, dan pola akses baris tetap identik:

import mssql_python
from datetime import date

def get_orders(customer_id: int, start_date: date):
    conn = mssql_python.connect(
        "Server=localhost;"
        "Database=AdventureWorks2022;"
        "Trusted_Connection=yes;"
    )
    cursor = conn.cursor()

    cursor.execute("""
        SELECT SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = ? AND OrderDate >= ?
        ORDER BY OrderDate DESC
    """, (customer_id, start_date))

    orders = []
    for row in cursor:
        orders.append({
            "id": row.SalesOrderID,
            "date": row.OrderDate,
            "total": row.TotalDue
        })

    cursor.close()
    conn.close()
    return orders

Satu-satunya perubahan adalah pernyataan impor dan string koneksi (tidak perlu DRIVER kata kunci). Setiap kueri, parameter, pola pengambilan, dan akses baris tetap identik.

Menguji migrasi

Sebelum menyelesaikan migrasi, jalankan kueri yang sama terhadap kedua driver dan bandingkan hasil untuk mengonfirmasi perilaku yang setara.

Memverifikasi perilaku yang setara

Gunakan fungsi perbandingan yang menjalankan kueri yang sama terhadap kedua driver dan menegaskan kecocokan hasilnya:

import pyodbc
import mssql_python

def compare_results(pyodbc_conn_str: str, mssql_conn_str: str, query: str):
    """Compare results from both drivers."""
    # pyodbc query
    pyodbc_conn = pyodbc.connect(pyodbc_conn_str)
    pyodbc_cursor = pyodbc_conn.cursor()
    pyodbc_cursor.execute(query)
    pyodbc_results = pyodbc_cursor.fetchall()
    pyodbc_conn.close()
    
    # mssql-python query
    mssql_conn = mssql_python.connect(mssql_conn_str)
    mssql_cursor = mssql_conn.cursor()
    mssql_cursor.execute(query)
    mssql_results = mssql_cursor.fetchall()
    mssql_conn.close()
    
    # Compare
    assert len(pyodbc_results) == len(mssql_results)
    for p_row, m_row in zip(pyodbc_results, mssql_results):
        assert tuple(p_row) == tuple(m_row)
    
    print(f"Results match: {len(pyodbc_results)} rows")

Checklist

  • [ ] Perbarui impor dari pyodbc ke mssql_python.
  • [ ] Hapus DRIVER= dari string koneksi.
  • [ ] Pertahankan kueri parameter ? yang ada (berfungsi tanpa perubahan).
  • [ ] Gunakan pernyataan EXECUTE untuk pemanggilan prosedur tersimpan.
  • [ ] Hapus konfigurasi pengumpulan koneksi eksternal.
  • [ ] Perbarui nama kelas penanganan pengecualian.
  • [ ] Uji semua kueri dan prosedur tersimpan.
  • [ ] Verifikasi penanganan tipe data (terutama desimal dan tanggal).
  • [ ] Hapus driver ODBC dari persyaratan penyebaran.