Mulai Cepat: Apache Arrow dengan driver mssql-python untuk Python

Dalam panduan singkat ini, gunakan metode pengambilan Arrow bawaan driver mssql-python untuk mengambil data SQL Server sebagai tabel Apache Arrow berformat kolom. Format memori berbasis kolom dari Arrow memungkinkan analitik berkinerja tinggi, interoperabilitas tanpa penyalinan data dengan pandas, Polars, dan DuckDB, serta I/O file Parquet yang efisien tanpa perlu membuat objek Python untuk setiap baris.

Driver mssql-python tidak memerlukan dependensi eksternal apa pun pada komputer Windows. Driver menginstal semua yang dibutuhkannya dengan satu pip instalasi, sehingga Anda dapat menggunakan driver versi terbaru untuk skrip baru tanpa merusak skrip lain yang tidak sempat Anda tingkatkan dan uji.

dokumentasi mssql-pythonkode sumber mssql-pythonPaket (PyPI)uv

Prasyarat

Instal prasyarat khusus sistem operasi satu kali. Pengguna Windows dapat melewati langkah ini. Untuk detail platform lengkap, lihat Menginstal mssql-python.

apk add libtool krb5-libs krb5-dev

Membuat database SQL

Buat atau sambungkan ke database SQL di salah satu platform berikut:

Membuat proyek dan menjalankan kode

  1. Membuat proyek baru
  2. Menambahkan dependensi
  3. Luncurkan Visual Studio Code
  4. Memperbarui pyproject.toml
  5. Memperbarui main.py
  6. Simpan string koneksi
  7. Menggunakan uv run untuk menjalankan skrip

Membuat proyek baru

  1. Buka jendela perintah di direktori pengembangan Anda. Jika Anda tidak memilikinya, buat direktori baru, seperti python atau scripts. Hindari folder di OneDrive Anda, karena sinkronisasi dapat mengganggu pengelolaan lingkungan virtual Anda.

  2. Membuat proyek baru dengan menggunakan uv.

    uv init arrow-qs
    cd arrow-qs
    

Tambah dependensi

Di direktori yang sama, instal mssql-pythonpaket , python-dotenv, pyarrow, dan rich .

uv add mssql-python python-dotenv pyarrow rich

Luncurkan Visual Studio Code

Di direktori yang sama, jalankan perintah berikut.

code .

Memperbarui pyproject.toml

  1. File pyproject.toml berisi metadata untuk proyek Anda. Buka file di editor favorit Anda.

  2. Tinjau isi dari file tersebut. Ini harus mirip dengan contoh ini. Perhatikan versi Python dan dependensi; untuk mssql-python, gunakan >= guna menentukan versi minimum. Jika Anda lebih suka versi yang tepat, ubah >= sebelum nomor versi menjadi ==. Versi yang diselesaikan dari setiap paket kemudian disimpan di uv.lock. Lockfile memastikan bahwa pengembang yang mengerjakan proyek menggunakan versi paket yang konsisten. Terapkan kedua pyproject.toml dan uv.lock, dan jalankan pemindai dependensi yang disetujui organisasi di CI. Jangan mengedit uv.lock file secara langsung.

    [project]
    name = "arrow-qs"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.11"
    dependencies = [
        "mssql-python>=1.5.0",
        "pyarrow>=19.0.0",
        "python-dotenv>=1.1.1",
        "rich>=14.1.0",
    ]
    
  3. Perbarui deskripsi agar lebih deskriptif.

    description = "Fetch SQL Server data as Apache Arrow tables using mssql-python"
    
  4. Simpan dan tutup file.

Memperbarui main.py

  1. Buka file bernama main.py. Ini harus mirip dengan contoh ini.

    def main():
        print("Hello from arrow-qs!")
    
    if __name__ == "__main__":
        main()
    
  2. Ganti seluruh konten main.py dengan kode berikut.

    """Fetch SQL Server data as Apache Arrow tables using mssql-python."""
    
    from os import getenv
    
    import pyarrow as pa
    import pyarrow.parquet as pq
    from dotenv import load_dotenv
    from mssql_python import connect, Connection
    from rich.console import Console
    from rich.table import Table
    
    console = Console()
    
    
    def get_connection() -> Connection:
        """Create a connection using the connection string from .env."""
        load_dotenv()
        conn_str = getenv("SQL_CONNECTION_STRING")
        if not conn_str:
            raise ValueError("SQL_CONNECTION_STRING not set in .env file")
        return connect(conn_str)
    
    
    def fetch_arrow_table(conn: Connection) -> pa.Table:
        """Run a query and return the full result as an Arrow Table."""
        cursor = conn.cursor()
        cursor.execute("""
            SELECT
                p.ProductID,
                p.Name,
                p.ProductNumber,
                p.Color,
                p.StandardCost,
                p.ListPrice,
                p.Size,
                p.Weight,
                p.SellStartDate,
                pc.Name AS Category
            FROM SalesLT.Product AS p
            INNER JOIN SalesLT.ProductCategory AS pc
                ON p.ProductCategoryID = pc.ProductCategoryID
            ORDER BY p.ListPrice DESC
        """)
        arrow_table = cursor.arrow()
        cursor.close()
        return arrow_table
    
    
    def fetch_arrow_batches(conn: Connection) -> pa.Table:
        """Stream results one batch at a time using arrow_batch()."""
        cursor = conn.cursor()
        cursor.execute("""
            SELECT
                c.CustomerID,
                c.CompanyName,
                c.EmailAddress,
                COUNT(soh.SalesOrderID) AS OrderCount,
                SUM(soh.SubTotal + soh.TaxAmt + soh.Freight) AS TotalSpent
            FROM SalesLT.Customer AS c
            LEFT OUTER JOIN SalesLT.SalesOrderHeader AS soh
                ON c.CustomerID = soh.CustomerID
            GROUP BY
                c.CustomerID,
                c.CompanyName,
                c.EmailAddress
            ORDER BY TotalSpent DESC
        """)
        batches = []
        while True:
            batch = cursor.arrow_batch()
            if batch is None or batch.num_rows == 0:
                break
            batches.append(batch)
        cursor.close()
    
        if not batches:
            return pa.table({})
    
        return pa.Table.from_batches(batches)
    
    
    def fetch_with_reader(conn: Connection) -> pa.Table:
        """Use arrow_reader() to stream results as a RecordBatchReader."""
        cursor = conn.cursor()
        cursor.execute("""
            SELECT
                soh.SalesOrderID,
                soh.OrderDate,
                (soh.SubTotal + soh.TaxAmt + soh.Freight) AS TotalDue,
                c.CompanyName
            FROM SalesLT.SalesOrderHeader AS soh
            INNER JOIN SalesLT.Customer AS c
                ON soh.CustomerID = c.CustomerID
            ORDER BY soh.OrderDate DESC
        """)
        reader = cursor.arrow_reader()
        arrow_table = reader.read_all()
        cursor.close()
        return arrow_table
    
    
    def display_arrow_table(arrow_table: pa.Table, title: str, max_rows: int = 10) -> None:
        """Display an Arrow table using rich formatting."""
        rich_table = Table(title=title)
    
        for name in arrow_table.column_names:
            rich_table.add_column(name, style="bright_white")
    
        for i in range(min(max_rows, arrow_table.num_rows)):
            row = [str(arrow_table.column(col)[i].as_py()) for col in range(arrow_table.num_columns)]
            rich_table.add_row(*row)
    
        if arrow_table.num_rows > max_rows:
            rich_table.add_row(*[f"... ({arrow_table.num_rows - max_rows} more rows)" if col == 0 else "" for col in range(arrow_table.num_columns)])
    
        console.print(rich_table)
        console.print(f"\n[dim]Schema: {arrow_table.num_columns} columns, {arrow_table.num_rows} rows[/dim]\n")
    
    
    def save_to_parquet(arrow_table: pa.Table, file_path: str) -> None:
        """Save an Arrow table to a Parquet file."""
        pq.write_table(arrow_table, file_path)
        console.print(f"[green]Saved {arrow_table.num_rows} rows to {file_path}[/green]\n")
    
    
    def main() -> None:
        conn = get_connection()
    
        # 1. Fetch entire result as an Arrow Table with cursor.arrow()
        console.rule("[bold]cursor.arrow() - Full table fetch[/bold]")
        products = fetch_arrow_table(conn)
        display_arrow_table(products, "Products (Top 10 by List Price)")
    
        # 2. Stream results in batches with cursor.arrow_batch()
        console.rule("[bold]cursor.arrow_batch() - Batch streaming[/bold]")
        customers = fetch_arrow_batches(conn)
        display_arrow_table(customers, "Customers by Total Spent")
    
        # 3. Use RecordBatchReader with cursor.arrow_reader()
        console.rule("[bold]cursor.arrow_reader() - RecordBatchReader[/bold]")
        orders = fetch_with_reader(conn)
        display_arrow_table(orders, "Recent Orders")
    
        # 4. Save to Parquet
        console.rule("[bold]Save to Parquet[/bold]")
        save_to_parquet(products, "products.parquet")
    
        # 5. Read back from Parquet and verify
        loaded = pq.read_table("products.parquet")
        console.print(f"[green]Read back {loaded.num_rows} rows from products.parquet[/green]")
        console.print(f"[dim]Schema: {loaded.schema}[/dim]\n")
    
        conn.close()
    
    
    if __name__ == "__main__":
        main()
    

Simpan string koneksi

  1. .gitignore Buka file dan tambahkan pengecualian untuk .env file. File Anda harus mirip dengan contoh ini. Pastikan untuk menyimpan dan menutupnya setelah selesai.

    # Python-generated files
    __pycache__/
    *.py[oc]
    build/
    dist/
    wheels/
    *.egg-info
    
    # Virtual environments
    .venv
    
    # Connection strings and secrets
    .env
    
    # Generated data files
    *.parquet
    
  2. Di direktori saat ini, buat file baru bernama .env.

  3. Di dalam file .env, tambahkan entri untuk string koneksi Anda yang bernama SQL_CONNECTION_STRING. Ganti contoh di sini dengan nilai string koneksi Anda yang sebenarnya.

    SQL_CONNECTION_STRING="Server=<server_name>;Database=<database_name>;Encrypt=yes;TrustServerCertificate=no;Authentication=ActiveDirectoryInteractive"
    

    Penting

    Jaga .env lokal dan di luar kendali sumber. Untuk CI dan lingkungan yang disebarkan, injeksikan string koneksi atau rahasia komponennya dari penyimpanan rahasia platform Anda alih-alih menyalin .env antar mesin.

    Tip

    string koneksi yang Anda gunakan sangat bergantung pada jenis database SQL yang Anda sambungkan. Jika Anda menyambungkan ke Azure SQL Database atau database SQL di Fabric, gunakan string koneksi ODBC dari tab string koneksi. Anda mungkin perlu menyesuaikan jenis autentikasi tergantung pada skenario Anda. Untuk informasi selengkapnya tentang string koneksi dan sintaksnya, lihat referensi sintaks string koneksi.

Menggunakan uv run untuk menjalankan skrip

Tip

Di macOS, baik ActiveDirectoryInteractive dan ActiveDirectoryDefault berfungsi untuk autentikasi Microsoft Entra. ActiveDirectoryInteractive meminta Anda untuk masuk setiap kali Anda menjalankan skrip. Untuk menghindari permintaan masuk berulang, masuk sekali melalui Azure CLI dengan menjalankan az login, lalu gunakan ActiveDirectoryDefault, yang menggunakan kembali kredensial yang di-cache.

  • Di jendela terminal dari sebelumnya, atau jendela terminal baru terbuka ke direktori yang sama, jalankan perintah berikut.

    uv run main.py
    

    Skrip menunjukkan tiga metode pengambilan data Arrow:

    • cursor.arrow() mengembalikan pyarrow.Table lengkap dengan semua baris. Terbaik untuk kumpulan hasil kecil hingga menengah di mana Anda memerlukan himpunan data lengkap dalam memori.

    • cursor.arrow_batch() mengembalikan pyarrow.RecordBatch satu per satu. Terbaik untuk kumpulan hasil besar di mana Anda ingin memproses data secara bertahap tanpa memuat semuanya ke dalam memori.

    • cursor.arrow_reader() mengembalikan sebuah pyarrow.RecordBatchReader untuk streaming. Terbaik untuk pemrosesan gaya alur atau meneruskan langsung ke pustaka yang menerima pembaca.

    Skrip juga menyimpan data produk ke file Parquet dan membacanya kembali untuk memverifikasi perjalanan pulang-pergi.

Cara kerja kode

  1. Koneksi: Skrip memuat string koneksi dari file .env dan membuat koneksi menggunakan mssql_python.connect().

  2. Pengambilan tabel penuh: cursor.arrow() menjalankan kueri dan mengembalikan seluruh kumpulan hasil sebagai .pyarrow.Table Driver mengonversi data di lapisan C++ menggunakan Arrow C Data Interface, tanpa membuat objek Python untuk meningkatkan kinerja.

  3. Batch streaming: cursor.arrow_batch() mengembalikan satu pyarrow.RecordBatch setiap panggilan. Loop mengumpulkan batch hingga tidak ada lagi baris yang tersisa, lalu menggabungkannya menjadi satu tabel. Gunakan pendekatan ini untuk himpunan data besar atau saat Anda ingin memproses setiap batch secara independen.

  4. RecordBatchReader: cursor.arrow_reader() mengembalikan sebuah pyarrow.RecordBatchReader, antarmuka Arrow standar yang dapat langsung diterima oleh banyak pustaka. Memanggil reader.read_all() akan membaca seluruh stream dan mengubahnya menjadi tabel.

  5. Parquet I/O: pyarrow.parquet.write_table() menyimpan tabel Arrow sebagai file Parquet terkompresi. Format ini mempertahankan jenis kolom dan mendukung pembacaan parsial yang efisien.

Langkah berikutnya

Gunakan artikel ini untuk terus membangun:

  • Integrasi Arrow untuk pola Arrow tingkat lanjut yang mencakup pemrosesan batch, pengelolaan memori, dan interoperabilitas pustaka.
  • integrasi pandas untuk memuat hasil kueri langsung ke DataFrames.
  • Integrasi Polars untuk membangun Polars DataFrames dari kueri asli Arrow.