mssql-pythonでデータアクセスと分析パターンを選択してください

mssql-pythonドライバーは、Microsoft SQLからデータを読み取るための複数のパスを提供します。 それぞれの道は異なる課題に対応しています。 このガイドは、あなたのデータ量、分析ニーズ、パフォーマンス要件に基づいて最適なものを選ぶのに役立ちます。

仕事量で決めてください

この表を使って出発点を見つけてください:

Workload 推奨パス なぜでしょうか
アプリケーション行アクセス(ウェブAPI、CRUD) カーソルフェッチ方法 低オーバーヘッド、行単位処理、追加の依存関係なし。
小規模から中規模のレポートクエリ pandas フィルタリング、グループ化、可視化のための馴染みのあるAPIです。
大きな結果セットまたは広いテーブル 矢印の抽出 ゼロコピーのカラム転送、最小限のメモリ負荷。
ハイ パフォーマンス分析 ポーラーズ・ウィズ・アロー カラムナー形式のデータでのマルチスレッド実行、GIL の競合なし。
ローカルおよびリモートデータ上のアドホックSQL DuckDBとArrow ArrowテーブルでのSQL分析、ローカルCSV/Parquetファイルとの結合。
ノートブック探索 pandas または Polars with Arrow チームの知識やデータ量に基づいて選びましょう。

カーソルの取得方法

追加の依存関係なしで行指向アクセスが必要な場合は標準カーソルメソッドを使いましょう。 このメソッドは、1行ずつ処理し、API応答を返す、またはアプリケーションロジックを送るアプリケーションコードに適しています。

import mssql_python

conn = mssql_python.connect(
    server="<server>.database.windows.net",
    database="<database>",
    authentication="ActiveDirectoryDefault",
    encrypt="yes"
)

cursor = conn.cursor()

# fetchone(): Process rows one at a time
cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ListPrice > %(threshold)s", {"threshold": 100})
row = cursor.fetchone()
while row:
    print(f"{row.Name}: ${row.ListPrice:.2f}")
    row = cursor.fetchone()

# fetchmany(): Process in batches
cursor.execute("SELECT ProductID, Name FROM Production.Product")
while True:
    batch = cursor.fetchmany(100)
    if not batch:
        break
    for row in batch:
        print(row.Name)

# fetchval(): Get a single scalar value
cursor.execute("SELECT COUNT(*) FROM Production.Product")
count = cursor.fetchval()

fetchmany()は、大規模な結果セットのメモリ効率の高いバッチ処理に活用してください。 カウント、マックス、存在チェックなど単一の数値が必要なときに fetchval() 使ってください。

フェッチメソッドの完全なドキュメントについては、データ 取得を参照してください。

矢印の抽出

分析やDataFrame構築、Parquetへのエクスポートに必要な列データが必要な場合はArrow抽出を使いましょう。 Arrowはドライバからのゼロコピーデータ転送を提供し、 fetchall()からデータフレームを構築する際の行ごとの変換オーバーヘッドを回避します。

カラムストアインデックスを持つテーブルは、すでにデータベースエンジン内でカラム形式で格納されているため、Arrow抽出はこれらのワークロードに自然に適しています。

cursor.execute("""
    SELECT ProductID, Name, ListPrice, Color
    FROM Production.Product
    WHERE ListPrice > 0
""")

# Get a single Arrow table
arrow_table = cursor.arrow()
print(f"{arrow_table.num_rows} rows, {arrow_table.num_columns} columns")

大規模な結果セットの場合、 arrow_reader() を使ってすべてをメモリにロードせずにバッチをストリーミングできます:

cursor.execute("SELECT * FROM Sales.SalesOrderHeader")

# Stream Arrow record batches
reader = cursor.arrow_reader(batch_size=10000)
for batch in reader:
    # Each batch is a pyarrow.RecordBatch
    print(f"Batch: {batch.num_rows} rows")

Arrowテーブルは、pandas、Polars、DuckDBの基盤です。 一度抽出してから変換します:

cursor.execute("""
    SELECT ProductID, Name, ListPrice, Color
    FROM Production.Product
    WHERE ListPrice > 0
""")
arrow_table = cursor.arrow()

# Arrow -> pandas
df = arrow_table.to_pandas()

# Arrow -> Polars (zero-copy)
import polars as pl
df = pl.from_arrow(arrow_table)

Arrowの完全なドキュメントについては、 Apache Arrow統合を参照してください。

pandas

報告、アドホック分析、データクリーンアップに慣れたDataFrame APIが必要なときはpandasを使いましょう。 Pandasはメモリに収まる結果セット(列幅によっては数百万行まで)で最もよく動作します。

cursor.execute("""
    SELECT p.Name, p.ListPrice, pc.Name AS Category
    FROM Production.Product p
    JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
    JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID
    WHERE p.ListPrice > 0
""")

import pandas as pd
rows = cursor.fetchall()
columns = [desc[0] for desc in cursor.description]
df = pd.DataFrame.from_records(rows, columns=columns)

# Analyze
print(df.groupby("Category")["ListPrice"].agg(["mean", "count"]))

結果セットが大きい場合は、fetchall() の代わりに Arrow から DataFrame を構築してください:

cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
arrow_table = cursor.arrow()
df = arrow_table.to_pandas()

ETL、時系列、書き込みバックを含む完全なパンダパターンについては、 パンダス統合を参照してください。

ポーラーズ・ウィズ・アロー

より大きな結果セットに対してより速いDataFrame操作が必要なときはPolarを使いましょう。 PolarsはApache Arrowをメモリ形式として使用しているため、 cursor.arrow() からの転送はゼロコピーです。 Polarsは複数のスレッドで操作を行うため、CPU負荷の高い変換でのGIL競合を回避できます。

import polars as pl

cursor.execute("""
    SELECT ProductID, Name, ListPrice, Color
    FROM Production.Product
    WHERE ListPrice > 0
""")

arrow_table = cursor.arrow()
df = pl.from_arrow(arrow_table)

# Filter and aggregate
result = (
    df.filter(pl.col("ListPrice") > 100)
    .group_by("Color")
    .agg(pl.col("ListPrice").mean().alias("AvgPrice"))
    .sort("AvgPrice", descending=True)
)
print(result)

大規模な結果セットのストリーミング:

cursor.execute("SELECT * FROM Sales.SalesOrderHeader")

reader = cursor.arrow_reader(batch_size=50000)
frames = []
for batch in reader:
    frames.append(pl.from_arrow(batch))

df = pl.concat(frames)

ポーラーズの完全なパターンについては、 ポーラーズ積分を参照してください。

DuckDBとArrow

抽出したデータに対してSQL分析を実行したり、サーバーデータをローカルのCSVやParquetファイルと結合したり、結果をファイル形式にエクスポートしたりする際にはDuckDBを使いましょう。 DuckDBはArrowテーブル上でゼロコピーアクセスで動作します。

import duckdb

cursor.execute("""
    SELECT ProductID, Name, ListPrice, Color
    FROM Production.Product
    WHERE ListPrice > 0
""")

products = cursor.arrow()

# Run DuckDB SQL on the Arrow table
result = duckdb.sql("""
    SELECT Color, AVG(ListPrice) AS AvgPrice, COUNT(*) AS Count
    FROM products
    WHERE Color IS NOT NULL
    GROUP BY Color
    ORDER BY AvgPrice DESC
""")
print(result.fetchdf())

サーバーデータをローカルファイルと結合する:

cursor.execute("SELECT CustomerID, TerritoryID FROM Sales.Customer")
customers = cursor.arrow()

# Join with a local CSV file
result = duckdb.sql("""
    SELECT c.CustomerID, c.TerritoryID, l.Region
    FROM customers c
    JOIN read_csv_auto('regions.csv') l ON c.TerritoryID = l.TerritoryID
""")

Parquet へエクスポート:

cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
orders = cursor.arrow()

duckdb.sql("COPY orders TO 'orders.parquet' (FORMAT PARQUET)")

DuckDBの完全なパターンについては、 DuckDB統合を参照してください。

リードパスの決定に影響を与えるMicrosoftのSQL機能

データベースエンジンには、どの読み取りパスが最も効果的かに直接影響する機能があります。 アプローチを選ぶ際には以下の特徴を考慮してください:

列ストア インデックス

カラムストアインデックスを持つテーブルは、カラム形式でデータを格納します。 これらのテーブルでは、データがエンジン内ですでに列指向形式になっているため、Arrow 形式での抽出が自然な受け渡し方法です。 分析クエリが数百万行の広いテーブルをスキャンする場合、サーバー側の非クラスタ化されたカラムストアインデックスとクライアント側のArrow抽出の組み合わせで、エンドツーエンドのスループットが最適です。

インデックス付きビュー

インデックスされたビューは 、集計された結果や結合された結果を事前に計算し、サーバー上に保存します。 もしpandasやPolarsの解析で同じ集計が繰り返される場合は、インデックス付きビューを作成し、そのビューにクエリを出すことを検討してください。 サーバーは、基になるデータが変更されても、ビューを自動的に最新の状態に保ちます。

クエリ ストア

クエリ ストアはクエリ実行の統計を時間経過とともに追跡します。 Arrow 抽出とローカルでの DataFrame 分析を行う価値があるほどコストの高いクエリと、直接カーソルで読み取るほうが適切なクエリを識別するために使用します。 クエリがミリ秒単位で動作するなら、カーソルフェッチで問題ありません。 もし数百万行をスキャンするなら、Arrow抽出やローカル解析でサーバー負荷を軽減できるかもしれません。

インテリジェントなクエリ処理

Microsoft SQLのインテリジェントクエリ処理機能、例えばアダプティブジョイン、rowstoreのバッチモード、メモリ付与フィードバックなどは、クエリ実行を自動的に最適化します。 これらの機能はどのクライアントリードパスを選んでも機能しますが、特に大規模な分析クエリに最も有利です。 ほとんどのワークロードではヒントや実行計画を調整する必要はありません。

大規模な結果セットをストリーミングする

メモリに収まらない結果セットには、ストリーミングパターンを使いましょう:

カーソルベースのストリーミングfetchmany()使用した:

cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
while True:
    batch = cursor.fetchmany(5000)
    if not batch:
        break
    for row in batch:
        print(row[0])  # Process each row

ParquetへのArrowベースのストリーミング :

import pyarrow.parquet as pq

cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
reader = cursor.arrow_reader(batch_size=50000)
writer = None

for batch in reader:
    if writer is None:
        writer = pq.ParquetWriter("orders.parquet", batch.schema)
    writer.write_batch(batch)

if writer:
    writer.close()

避けるべきアンチパターン

アンチパターン 問題 より優れたアプローチ
fetchall() 次に pd.DataFrame() 大きなテーブルの場合 すべての行を2回(1回はタプル、もう1回はデータフレーム)メモリに読み込みます。 cursor.arrow()を使い、その後arrow_table.to_pandas()
行をフィルタリングするためだけにArrowをpandasに変換する pandasの完全なコピーにメモリを浪費します。 SQLでフィルタリング(WHERE 節)を使うか、Arrowテーブルに直接PolarsやDuckDBを使うのが良いでしょう。
SELECT * 3列必要なとき サーバーから不要なデータを転送します。 必要な列だけをリストアップしてください。
計算のためのデータフレームの構築 COUNT(*) サーバーはPythonよりも速く集計を計算します。 SELECT COUNT(*)fetchval()を使用します。
クエリごとに新しい接続を開く 接続の作成は、プーリングのオーバーヘッドを考慮しても高コストです。 論理的な作業単位内で接続を再利用すること。
チェイニングアロー -> パンダ -> ポーラーズ 各変換はデータをコピーします。 目的の形式に直接変換できます: Arrow -> Polars または Arrow -> pandas。