Быстрый старт: Apache Arrow с драйвером mssql-python для Python

В этом кратком руководстве используйте встроенные в драйвер mssql-python методы получения данных Arrow для извлечения данных SQL Server в виде колоночных таблиц Apache Arrow. Колоночный формат данных в памяти Arrow обеспечивает высокопроизводительную аналитику, взаимодействие без копирования данных с pandas, Polars и DuckDB, а также эффективный ввод-вывод файлов в формате Parquet без построчного создания объектов Python.

Драйвер mssql-python не требует внешних зависимостей на компьютерах Windows. Драйвер устанавливает всё необходимое за одну pip установку, так что вы можете использовать последнюю версию драйвера для новых скриптов, не ломая другие скрипты, на которые нет времени обновить и протестировать.

Документация | Исходный код mssql-python | Пакет (PyPI) | uv

Необходимые условия

Установите единовременные предварительные условия для операционной системы. Пользователи Windows могут пропустить этот шаг. Для полной информации о платформе см. Установить mssql-python.

apk add libtool krb5-libs krb5-dev

Создание базы данных SQL

Создайте или подключитесь к SQL-базе данных на одной из следующих платформ:

Создание проекта и запуск кода

  1. Создание нового проекта
  2. Добавление зависимостей
  3. Запуск Visual Studio Code
  4. Обновление pyproject.toml
  5. Обновление main.py
  6. Сохранение строки подключения
  7. Использование uv run для выполнения скрипта

Создание нового проекта

  1. Откройте командную строку в каталоге разработки. Если у вас его нет, создайте новую директорию, например python или scripts. Избегайте папок на OneDrive, так как синхронизация может мешать управлению виртуальной средой.

  2. Создайте новый проект , используя uv.

    uv init arrow-qs
    cd arrow-qs
    

Добавление зависимостей

В той же папке установите mssql-python, python-dotenv, pyarrow, и rich пакеты.

uv add mssql-python python-dotenv pyarrow rich

Запустите Visual Studio Code.

В том же каталоге выполните следующую команду.

code .

Обновление pyproject.toml

  1. Файл pyproject.toml содержит метаданные вашего проекта. Откройте файл в избранном редакторе.

  2. Просмотрите содержимое файла. Он должен быть похож на этот пример. Обратите внимание на версию Python и зависимость для mssql-python; используйте >=, чтобы указать минимальную версию. Если вы предпочитаете точную версию, измените >= на == перед номером версии. Затем разрешенные версии каждого пакета хранятся в uv.lock. Файл блокировки гарантирует, что разработчики, работающие над проектом, используют единообразные версии пакетов. Зафиксируйте оба pyproject.toml и uv.lock, и запустите одобренный организацией сканер зависимостей в CI. Не редактируйте uv.lock файл напрямую.

    [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. Обновите описание, чтобы быть более описательным.

    description = "Fetch SQL Server data as Apache Arrow tables using mssql-python"
    
  4. Сохраните и закройте файл.

Обновление main.py

  1. Откройте файл с именем main.py. Он должен быть похож на этот пример.

    def main():
        print("Hello from arrow-qs!")
    
    if __name__ == "__main__":
        main()
    
  2. Замените все содержимое main.py следующим кодом.

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

Сохранение строки подключения

  1. .gitignore Откройте файл и добавьте исключение для .env файлов. Файл должен быть похож на этот пример. Не забудьте сохранить и закрыть его после завершения.

    # Python-generated files
    __pycache__/
    *.py[oc]
    build/
    dist/
    wheels/
    *.egg-info
    
    # Virtual environments
    .venv
    
    # Connection strings and secrets
    .env
    
    # Generated data files
    *.parquet
    
  2. В текущем каталоге создайте новый файл с именем .env.

  3. В файле .env добавьте запись для строки подключения с именем SQL_CONNECTION_STRING. Замените пример фактическим значением строки подключения.

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

    Important

    Храните .env локально и вне системы контроля версий. Для CI и развернутых окружений передавайте строку подключения или входящие в неё секреты из хранилища секретов вашей платформы вместо копирования .env между машинами.

    Tip

    Используемая вами строка подключения во многом зависит от типа SQL-базы данных, к которой вы подключаетесь. Если вы подключаетесь к базе данных SQL Azure или базе данных SQL в Fabric, используйте строку подключения ODBC на вкладке строк подключения. Возможно, вам потребуется настроить тип проверки подлинности в зависимости от вашего сценария. Дополнительные сведения о строках подключения и их синтаксисе см. в справочнике по синтаксису строки подключения.

Использование uv run для выполнения скрипта

Tip

На macOS, оба ActiveDirectoryInteractive и ActiveDirectoryDefault работают для проверки подлинности Microsoft Entra. ActiveDirectoryInteractive запрашивает вход при каждом запуске скрипта. Чтобы избежать повторных запросов при входе, войдите один раз через Azure CLI, запустив az login, затем используйте ActiveDirectoryDefault, который повторно использует кэшированный учетный код.

  • В окне терминала до или в новом окне терминала, открываемом в том же каталоге, выполните следующую команду.

    uv run main.py
    

    Скрипт демонстрирует три метода получения Arrow:

    • cursor.arrow() возвращает полный pyarrow.Table со всеми строками. Лучше всего подходит для небольших и средних наборов результатов, где нужен полный набор данных в памяти.

    • cursor.arrow_batch() возвращает по одному pyarrow.RecordBatch. Лучше всего подходит для больших наборов результатов, где нужно обрабатывать данные постепенно, не загружая всё в память.

    • cursor.arrow_reader() возвращает pyarrow.RecordBatchReader для потоковой передачи. Лучше всего подходит для обработки в стиле конвейера или для передачи напрямую библиотекам, принимающим считыватель.

    Скрипт также сохраняет данные продукта в файл Parquet и читает их обратно для проверки кругового обращения.

Как работает код

  1. Connection: скрипт загружает строку подключения из файла .env и создаёт соединение с помощью mssql_python.connect().

  2. Полная выборка таблицы: cursor.arrow() выполняет запрос и возвращает весь результирующий набор в виде pyarrow.Table. Драйвер преобразует данные на уровне C++ с помощью интерфейса данных Arrow C, обходя создание объектов на Python для повышения производительности.

  3. Пакетная трансляция: cursor.arrow_batch() возвращает один pyarrow.RecordBatch на один звонок. Цикл собирает партии до тех пор, пока не останется больше строк, затем объединяет их в одну таблицу. Используйте этот подход для больших наборов данных или когда хотите обрабатывать каждый пакет отдельно.

  4. RecordBatchReader: cursor.arrow_reader() возвращает pyarrow.RecordBatchReader, стандартный интерфейс Arrow, который многие библиотеки принимают напрямую. Вызов reader.read_all() поглощает весь поток в таблице.

  5. Parquet I/O: pyarrow.parquet.write_table() сохраняет таблицу Arrow в сжатый файл Parquet. Этот формат сохраняет типы столбцов и поддерживает эффективное частичное чтение.

Дальнейшие действия

Используйте эти статьи, чтобы продолжать строить:

  • Интеграция Arrow для продвинутых паттернов Arrow, включая пакетную обработку, управление памятью и взаимодействие с библиотеками.
  • интеграция с pandas для загрузки результатов запросов напрямую в датафреймы.
  • Интеграция Polars для построения Polars DataFrames на основе нативных Arrow запросов.