Início rápido: Apache Arrow com o driver mssql-python para Python

Neste guia de início rápido, utilize os métodos incorporados de obtenção do Arrow do controlador mssql-python para recuperar dados do SQL Server na forma de tabelas colunares do Apache Arrow. O formato de memória colunar do Arrow permite análises de alto desempenho, interoperabilidade sem cópia com pandas, Polars e DuckDB, e entrada/saída eficiente de ficheiros Parquet sem criar objetos Python linha a linha.

O mssql-python driver não requer nenhuma dependência externa em máquinas Windows. O driver instala tudo o que precisa com uma única pip instalação, por isso podes usar a versão mais recente do driver para novos scripts sem estragar outros scripts que não tens tempo de atualizar e testar.

Documentação | Código-fonte | Pacote (PyPI) | UV

Pré-requisitos

Pré-requisitos para instalar o sistema operativo

Instale pré-requisitos que devem ser instalados uma única vez específicos do sistema operacional. Os utilizadores do Windows podem saltar este passo. Para detalhes completos da plataforma, consulte Instalar mssql-python.

apk add libtool krb5-libs krb5-dev libstdc++

Criar um banco de dados SQL

Criar ou ligar-se a uma base de dados SQL numa das seguintes plataformas:

Crie o projeto e execute o código

Criar um novo projeto

  1. Abra um prompt de comando no diretório de desenvolvimento. Se não tiver um, crie um novo diretório, como python ou scripts. Evite pastas no seu OneDrive, pois a sincronização pode interferir na gestão do seu ambiente virtual.

  2. Crie um novo projeto usando uv.

    uv init arrow-qs
    cd arrow-qs
    

Adicionar dependências

No mesmo diretório, instale os pacotes mssql-python, python-dotenv, pyarrow e rich.

uv add mssql-python python-dotenv pyarrow rich

Abra o Visual Studio Code.

No mesmo diretório, execute o seguinte comando.

code .

Atualizar pyproject.toml

  1. Abra o pyproject.toml arquivo no editor. Este ficheiro contém os metadados do seu projeto.

  2. Revise o conteúdo do arquivo. Deve ser semelhante a este exemplo. Tenha em atenção a versão e a dependência do Python; para mssql-python, use >= para definir uma versão mínima. Se preferir uma versão exata, altere o >= para == antes do número da versão. As versões resolvidas de cada pacote são então armazenadas no uv.lock. O ficheiro de bloqueio garante que os programadores que trabalham no projeto utilizam versões consistentes dos pacotes. Registe ambos pyproject.toml e uv.lock e execute um analisador de dependências aprovado pela organização em CI. Não edites o uv.lock ficheiro diretamente.

    [project]
    name = "arrow-qs"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.11"
    dependencies = [
        "mssql-python>=1.16.0",
        "pyarrow>=19.0.0",
        "python-dotenv>=1.1.1",
        "rich>=14.1.0",
    ]
    
  3. Atualize o valor description.

    description = "Fetch SQL Server data as Apache Arrow tables using mssql-python"
    
  4. Salve e feche o arquivo.

Atualizar main.py

  1. Abra o arquivo chamado main.py. Deve ser semelhante a este exemplo.

    def main():
        print("Hello from arrow-qs!")
    
    if __name__ == "__main__":
        main()
    
  2. Substitua todo o conteúdo de main.py pelo seguinte código.

    """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:
        """Stream results with arrow_reader() and combine the batches."""
        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()
        batches = list(reader)
        cursor.close()
    
        if not batches:
            return pa.table({})
    
        return pa.Table.from_batches(batches)
    
    
    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. Stream results with cursor.arrow_reader()
        console.rule("[bold]cursor.arrow_reader() - Reader streaming[/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()
    

Salvar a cadeia de conexão

  1. Abra o .gitignore arquivo e adicione uma exclusão para .env arquivos. O seu ficheiro deve ser semelhante ao exemplo seguinte.

    # Python-generated files
    __pycache__/
    *.py[oc]
    build/
    dist/
    wheels/
    *.egg-info
    
    # Virtual environments
    .venv
    
    # Connection strings and secrets
    .env
    
    # Generated data files
    *.parquet
    
  2. Salve e feche .gitignore.

  3. No diretório atual, crie um novo arquivo chamado .env.

  4. Dentro do .env arquivo, adicione uma entrada para sua cadeia de conexão chamada SQL_CONNECTION_STRING. Substitua os espaços reservados pelos seus valores reais.

    SQL_CONNECTION_STRING="Server=<server>;Database=<database>;Encrypt=yes;TrustServerCertificate=no;Authentication=ActiveDirectoryInteractive"
    

    Importante

    Mantenha .env localmente e fora do controlo de código-fonte. Para CI e ambientes de implementação, injete a cadeia de ligação ou os segredos que a compõem a partir do repositório de segredos da sua plataforma, em vez de copiar .env de uma máquina para outra.

    A cadeia de ligação depende do tipo de base de dados SQL a que estás a ligar. Para Base de Dados SQL do Azure ou base de dados SQL no Fabric, use a cadeia de ligação ODBC do separador cadeia de ligaçãos e ajuste o tipo de autenticação para o seu cenário. Para mais informações sobre a sintaxe das cadeia de ligação, veja Connection strings for mssql-python.

    Sugestão

    No macOS, tanto ActiveDirectoryInteractive como ActiveDirectoryDefault funcionam para a autenticação do Microsoft Entra. ActiveDirectoryInteractive Solicita-te para iniciar sessão sempre que executas o script. Para evitar repetidos prompts de início de sessão, inicie sessão uma vez com o comando CLI do Azureaz login e use ActiveDirectoryDefault. ActiveDirectoryDefault Reutiliza a credencial em cache.

Use uv run para executar o script

Na janela do terminal anterior ou em uma nova janela do terminal aberta no mesmo diretório, execute o seguinte comando.

uv run main.py

O script demonstra três métodos de obtenção do Arrow:

  • cursor.arrow() devolve um pyarrow.Table completo com todas as linhas. Usa-o para conjuntos de resultados pequenos a médios onde precisas do conjunto de dados completo na memória.

  • cursor.arrow_batch() devolve um pyarrow.RecordBatch de cada vez. Use-o para grandes conjuntos de resultados onde pretende processar dados de forma incremental sem carregar tudo na memória.

  • cursor.arrow_reader() devolve um leitor em streaming que gera pyarrow.RecordBatch objetos à medida que o iteras. Utilize-o para processamento em pipeline ou para o fornecer diretamente a bibliotecas que aceitem um leitor.

O script também guarda os dados do produto num ficheiro Parquet e lê-los para verificar a viagem de ida e volta.

Como funciona o código

  1. Ligação: O script carrega a cadeia de ligação a partir de um .env ficheiro e cria uma ligação usando mssql_python.connect().

  2. Busca da tabela completa: cursor.arrow() executa a consulta e devolve todo o conjunto de resultados como um pyarrow.Table. O driver converte dados na sua camada C++ usando a Arrow C Data Interface, contornando a criação de objetos em Python para melhorar o desempenho.

  3. Transmissão em lote: cursor.arrow_batch() retorna um pyarrow.RecordBatch por chamada. O ciclo recolhe lotes até não restar mais linhas, depois junta-os numa única tabela. Use esta abordagem para conjuntos de dados grandes ou quando quiser processar cada lote de forma independente.

  4. Leitor em streaming: cursor.arrow_reader() devolve um leitor que gera pyarrow.RecordBatch objetos à medida que o iteras. Em comparação com o loop arrow_batch(), o leitor gere o seu próprio estado de iteração, pelo que não é necessário testar manualmente se o conjunto de resultados está esgotado.

    Esta função lê o fluxo até ao fim e depois fecha o cursor, por isso não precisa de fechar o leitor. Fecha o leitor quando parares de ler cedo e continua a usar o cursor. Para mais informações, consulte integração com o Apache Arrow.

  5. Parquet I/O: pyarrow.parquet.write_table() guarda a tabela Arrow num ficheiro Parquet comprimido. Este formato preserva os tipos de colunas e suporta leituras parciais eficientes.