퀵스타트: Python용 mssql-python 드라이버를 탑재한 Apache Arrow

이 퀵스타트에서는 드라이버에 내장된 Arrow fetch 메서드를 사용하여 mssql-python SQL Server 데이터를 열형 Apache Arrow 테이블로 불러옵니다. Arrow의 열 지향 메모리 형식은 고성능 분석, pandas, Polars, DuckDB와의 제로 카피 상호 운용, 그리고 행 단위의 Python 객체를 생성하지 않고도 효율적인 Parquet 파일 I/O를 가능하게 합니다.

드라이버에는 mssql-python Windows 컴퓨터에 대한 외부 종속성이 필요하지 않습니다. 드라이버는 필요한 모든 것을 한 번 pip 에 설치해주기 때문에, 업그레이드나 테스트할 시간이 없는 다른 스크립트를 망치지 않고 최신 버전의 드라이버를 새 스크립트에 사용할 수 있습니다.

mssql-python 설명서 | mssql-python 소스 코드 | 패키지(PyPI) | Uv

사전 요구 사항

일회성 운영 체제 관련 필수 구성 요소를 설치합니다. Windows 사용자는 이 단계를 건너뛸 수 있습니다. 전체 플랫폼 세부사항은 Install 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-pythonpython-dotenvpyarrow 패키지를 rich설치하세요.

uv add mssql-python python-dotenv pyarrow rich

Visual Studio Code 시작

동일한 디렉터리에서 다음 명령을 실행합니다.

code .

pyproject.toml 업데이트

  1. pyproject.toml 파일에는 프로젝트의 메타데이터가 포함되어 있습니다. 즐겨 찾는 편집기에서 파일을 엽니다.

  2. 파일의 내용을 검토합니다. 이 예제와 유사해야 합니다. mssql-python의 Python 버전과 의존성을 확인하고, 최소 버전을 정의하려면 >=를 사용하세요. 정확한 버전을 선호하는 경우, 버전 번호 앞의 >===로 변경하십시오. 그런 다음 각 패키지의 확인된 버전이 uv.lock에 저장됩니다. lockfile은 프로젝트 개발자들이 일관된 패키지 버전을 사용하도록 보장합니다. 두 가지 모두 pyproject.tomluv.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"
    

    중요합니다

    .env를 로컬에만 두고 소스 제어에 포함하지 마세요. CI 및 배포 환경에서는 .env를 기기 간에 복사하는 대신, 플랫폼 비밀 저장소에서 연결 문자열 또는 그 구성 요소 비밀 값을 주입하세요.

    팁 (조언)

    사용하는 연결 문자열은 주로 연결하는 SQL 데이터베이스 유형에 따라 달라집니다. Azure SQL Database 또는 Fabric의 SQL 데이터베이스에 연결하는 경우 연결 문자열 탭에서 ODBC 연결 문자열을 사용합니다. 시나리오에 따라 인증 유형을 조정해야 할 수도 있습니다. 연결 문자열 및 해당 구문에 대한 자세한 내용은 연결 문자열 구문 참조를 참조하세요.

uv run을 사용하여 스크립트 실행

팁 (조언)

macOS에서는 Microsoft Entra 인증을 위해 ActiveDirectoryInteractiveActiveDirectoryDefault가 모두 작동합니다. 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(으)로 반환합니다. 드라이버는 Arrow C 데이터 인터페이스를 사용하여 C++ 계층의 데이터를 변환하여 Python 객체 생성을 우회하여 성능을 향상시킵니다.

  3. 배치 스트리밍: cursor.arrow_batch()는 호출당 하나의 pyarrow.RecordBatch를 반환합니다. 루프는 더 이상 행이 남지 않을 때까지 배치를 수집한 뒤, 이를 하나의 테이블로 결합합니다. 이 방법은 대규모 데이터셋이나 각 배치를 독립적으로 처리하고 싶을 때 사용하세요.

  4. RecordBatchReader: cursor.arrow_reader() 는 표준 Arrow 인터페이스를 반환 pyarrow.RecordBatchReader하며, 많은 라이브러리에서 직접 지원합니다. reader.read_all()를 호출하면 전체 스트림을 테이블로 소비합니다.

  5. Parquet I/O: pyarrow.parquet.write_table() Arrow 테이블을 압축된 Parquet 파일에 저장합니다. 이 형식은 컬럼 유형을 보존하고 효율적인 부분 읽기를 지원합니다.

다음 단계

이 글들을 활용해 계속 성장하세요:

  • Arrow 연동: 배치 처리, 메모리 관리, 라이브러리 상호 운용성을 포함한 고급 Arrow 패턴 지원용.
  • pandas 통합 을 통해 쿼리 결과를 데이터프레임에 직접 불러오는 방식입니다.
  • Arrow 네이티브 쿼리에서 Polars 데이터프레임을 구축하기 위한 Polars 통합.