Démarrage rapide : Apache Arrow avec le pilote mssql-python pour Python

Dans ce démarrage rapide, utilisez les mssql-python méthodes Arrow fetch intégrées au pilote pour récupérer les données de SQL Server sous forme de tables Apache Arrow en colonnes. Le format mémoire colonnaire d'Arrow permet des analyses haute performance, une interopérabilité zéro copie avec pandas, Polar et DuckDB, ainsi qu'une E/S efficace de fichiers Parquet sans création d'objets Python ligne par ligne.

Le mssql-python pilote ne nécessite aucune dépendance externe sur les machines Windows. Le pilote installe tout ce dont il a besoin en une seule pip installation, donc vous pouvez utiliser la dernière version du pilote pour de nouveaux scripts sans casser d’autres scripts que vous n’avez pas le temps de mettre à jour et de tester.

documentation | code | Package (PyPI) | Uv

Logiciels requis

Installez les prérequis ponctuels spécifiques au système d'exploitation. Les utilisateurs Windows peuvent sauter cette étape. Pour tous les détails sur la plateforme, voir Installer mssql-python.

apk add libtool krb5-libs krb5-dev

Créer une base de données SQL

Créer ou connecter une base de données SQL sur l’une des plateformes suivantes :

Créer le projet et exécuter le code

  1. Créer un projet
  2. Ajouter des dépendances
  3. Lancer Visual Studio Code
  4. Mettre à jour pyproject.toml
  5. Mettre à jour main.py
  6. Enregistrer la chaîne de connexion
  7. Utilisez uv run pour exécuter le script

Créer un projet

  1. Ouvrez une invite de commandes dans votre répertoire de développement. Si vous n’en avez pas, créez un nouveau répertoire, tel que python ou scripts. Évitez les dossiers sur votre OneDrive, car la synchronisation peut interférer avec la gestion de votre environnement virtuel.

  2. Créez un nouveau projet en utilisant uv.

    uv init arrow-qs
    cd arrow-qs
    

Ajout de dépendances

Dans le même répertoire, installez les mssql-pythonpaquets , python-dotenv, pyarrow, et rich .

uv add mssql-python python-dotenv pyarrow rich

Lancer Visual Studio Code

Dans le même répertoire, exécutez la commande suivante.

code .

Mettre à jour pyproject.toml

  1. Le fichier pyproject.toml contient les métadonnées de votre projet. Ouvrez le fichier dans votre éditeur favori.

  2. Passez en revue le contenu du fichier. Il doit être similaire à cet exemple. Indiquez la version de Python et la dépendance pour mssql-python ; utilisez >= pour définir une version minimale. Si vous préférez une version exacte, changez le >= avant le numéro de version en ==. Les versions résolues de chaque package sont ensuite stockées dans uv.lock. Le fichier verrouillé garantit que les développeurs travaillant sur le projet utilisent des versions cohérentes des paquets. Validez pyproject.toml et uv.lock, et exécutez un analyseur de dépendances approuvé par l’organisation dans la CI. Ne modifie pas directement le uv.lock fichier.

    [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. Mettez à jour la description pour être plus descriptive.

    description = "Fetch SQL Server data as Apache Arrow tables using mssql-python"
    
  4. Enregistrez et fermez le fichier.

Mettre à jour main.py

  1. Ouvrez le fichier nommé main.py. Il doit être similaire à cet exemple.

    def main():
        print("Hello from arrow-qs!")
    
    if __name__ == "__main__":
        main()
    
  2. Remplacez tout le contenu de main.py par le code suivant.

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

Enregistrer la chaîne de connexion

  1. Ouvrez le .gitignore fichier et ajoutez une exclusion pour .env les fichiers. Votre fichier doit être similaire à cet exemple. Veillez à l’enregistrer et à le fermer lorsque vous avez terminé.

    # Python-generated files
    __pycache__/
    *.py[oc]
    build/
    dist/
    wheels/
    *.egg-info
    
    # Virtual environments
    .venv
    
    # Connection strings and secrets
    .env
    
    # Generated data files
    *.parquet
    
  2. Dans le répertoire actif, créez un fichier nommé .env.

  3. Dans le .env fichier, ajoutez une entrée pour votre chaîne de connexion nommée SQL_CONNECTION_STRING. Remplacez l’exemple ici par votre valeur de chaîne de connexion réelle.

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

    Important

    Gardez .env en local et hors de la gestion de version. Pour les environnements CI et déployés, injectez la chaîne de connexion ou les secrets qui la composent depuis le magasin de secrets de votre plateforme au lieu de copier .env d’une machine à l’autre.

    Tip

    La chaîne de connexion que vous utilisez dépend en grande partie du type de base de données SQL à laquelle vous vous connectez. Si vous vous connectez à une base de données Azure SQL ou à une base de données SQL dans Fabric, utilisez la chaîne de connexion ODBC à partir de l’onglet chaînes de connexion. Vous devrez peut-être ajuster le type d’authentification en fonction de votre scénario. Pour plus d’informations sur les chaînes de connexion et leur syntaxe, consultez la référence de la syntaxe des chaînes de connexion.

Utilisez uv run pour exécuter le script

Tip

Sur macOS, les deux ActiveDirectoryInteractive et ActiveDirectoryDefault fonctionnent pour l’authentification Microsoft Entra. ActiveDirectoryInteractive vous invite à vous connecter chaque fois que vous exécutez le script. Pour éviter les demandes de connexion répétées, connectez-vous une fois via l’interface Azure CLI en exécutant az login, puis utilisez ActiveDirectoryDefault, qui réutilise l’identifiant mis en cache.

  • Dans la fenêtre de terminal à partir de l’avant, ou une nouvelle fenêtre de terminal ouverte au même répertoire, exécutez la commande suivante.

    uv run main.py
    

    Le script présente trois méthodes de récupération d’Arrow :

    • cursor.arrow() renvoie un pyarrow.Table complet avec toutes les lignes. C’est idéal pour les ensembles de résultats petits à moyens où il faut garder l’ensemble de données en mémoire.

    • cursor.arrow_batch() renvoie un pyarrow.RecordBatch à la fois. C’est idéal pour de grands ensembles de résultats où l’on veut traiter les données de façon incrémentale sans tout charger en mémoire.

    • cursor.arrow_reader() renvoie un pyarrow.RecordBatchReader pour le streaming. Idéal pour le traitement en pipeline ou le passage direct aux bibliothèques qui acceptent un lecteur.

    Le script sauvegarde également les données produit dans un fichier Parquet et les relit pour vérifier le trajet aller-retour.

Fonctionnement du code

  1. Connexion : Le script charge la chaîne de connexion depuis un .env fichier et crée une connexion en utilisant mssql_python.connect().

  2. Recherche de table complète : cursor.arrow() exécute la requête et retourne l’ensemble des résultats sous forme de fichier pyarrow.Table. Le pilote convertit les données dans sa couche C++ en utilisant l’interface de données Arrow C, contournant la création d’objets Python pour améliorer les performances.

  3. Diffusion en série : cursor.arrow_batch() en retourne un pyarrow.RecordBatch par appel. La boucle collecte des lots jusqu’à ce qu’il ne reste plus de lignes, puis les combine en une seule table. Utilisez cette approche pour de grands ensembles de données ou lorsque vous souhaitez traiter chaque lot indépendamment.

  4. RecordBatchReader : cursor.arrow_reader() renvoie un pyarrow.RecordBatchReader, une interface Arrow standard que de nombreuses bibliothèques acceptent directement. L’appel à reader.read_all() consomme l’intégralité du flux pour le convertir en table.

  5. E/S Parquet : pyarrow.parquet.write_table() sauvegarde la table Arrow dans un fichier Parquet compressé. Ce format préserve les types de colonnes et prend en charge des lectures partielles efficaces.

Étapes suivantes

Utilisez ces articles pour continuer à construire :

  • Intégration Arrow pour des cas d’usage avancés d’Arrow, notamment le traitement par lots, la gestion de la mémoire et l’interopérabilité entre bibliothèques.
  • intégration de pandas pour charger directement les résultats des requêtes dans des DataFrames.
  • Intégration de Polars pour créer des DataFrames Polars à partir de requêtes Arrow natives.