Välj ett dataåtkomst- och analysmönster med mssql-python

Drivrutinen mssql-python erbjuder flera vägar för att läsa data från Microsoft SQL. Varje väg passar olika arbetsbelastningar. Denna guide hjälper dig att välja rätt baserat på din datastorlek, analysbehov och prestandakrav.

Bestäm efter arbetsbelastning

Använd denna tabell för att hitta din utgångspunkt:

Arbetsbörda Rekommenderad sökväg Varför
Applikationsradåtkomst (webb-API, CRUD) Metoder för att hämta markörer Låg resursåtgång, radvis bearbetning, inga extra beroenden.
Små till medelstora rapporteringsfrågor pandas Bekant API för filtrering, gruppering och visualisering.
Stora resultatuppsättningar eller breda tabeller Pilextraktion Nollkopieöverföring av kolumn, minimal minnesöverhead.
Analys med höga prestanda Polars med Arrow Multitrådad exekvering på kolumndata, ingen GIL-konflikt.
Ad hoc SQL över lokal och fjärrdata DuckDB med pil SQL-analys på Arrow-tabeller, koppla ihop med lokala CSV/Parquet-filer.
Utforskning av anteckningsböcker Pandor eller Polars med pil Välj baserat på teamets kännedom och datastorlek.

Metoder för att hämta markörer

Använd standardmetoder för markörer när du behöver radorienterad åtkomst utan extra beroenden. Denna metod är rätt val för applikationskod som behandlar en rad i taget, returnerar API-svar eller matar applikationslogik.

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

Användning fetchmany() för minneseffektiv batchbearbetning av stora resultatmängder. Använd fetchval() när du behöver ett enskilt värde, till exempel ett antal, ett maxvärde eller en kontroll av om något finns.

För fullständig dokumentation av hämtametoden, se Hämta data.

Pilutvinning

Använd Arrow-extraktion när du behöver kolumndata för analys, DataFrame-konstruktion eller export till Parquet. Arrow tillhandahåller dataöverföring utan kopiering från drivrutinen, vilket undviker omkostnaden för rad-för-rad-konvertering när man bygger en DataFrame från fetchall().

Tabeller med kolumnlagringsindex lagras redan i kolumnformat i databasmotorn, vilket gör Arrow-extraktion till en naturlig lösning för dessa arbetsbelastningar.

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

För stora resultatmängder, använd arrow_reader() för att strömma batcher utan att ladda in allt i minnet:

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-tabeller är utgångspunkten för pandas, Polars och DuckDB. Extrahera en gång, konvertera sedan:

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)

För fullständig Arrow-dokumentation, se Apache Arrow-integration.

pandas

Använd pandas när du behöver ett välbekant DataFrame API för rapportering, ad hoc-analys eller datarensning. Pandas fungerar bäst med resultatuppsättningar som får plats i minnet (upp till några miljoner rader, beroende på kolumnbredd).

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

För större resultatmängder, bygg DataFrame från Arrow istället för fetchall():

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

För kompletta pandas-mönster inklusive ETL, tidsserier och writeback, se pandas-integration.

Polare med pil

Använd Polars när du behöver snabbare DataFrame-operationer på större resultatuppsättningar. Polars använder Apache Arrow som minnesformat, så överföringen från cursor.arrow() är nollkopia. Polars kör också operationer på flera trådar, vilket undviker GIL-konflikter vid CPU-tunga transformationer.

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)

För strömning av stora resultatmängder:

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)

För kompletta Polars-mönster, se Polars-integration.

DuckDB med pil

Använd DuckDB när du behöver köra SQL-analys på extraherad data, koppla serverdata till lokala CSV- eller Parquet-filer, eller exportera resultat till filformat. DuckDB arbetar med piltabeller med noll-kopieringsåtkomst.

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

Koppla ihop serverdata med en lokal fil:

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

Exportera till Parquet:

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

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

För fullständiga DuckDB-mönster, se DuckDB-integration.

Microsoft SQL-funktioner som påverkar beslut om läsväg

Databasmotorn har funktioner som direkt påverkar vilken läsväg som fungerar bäst. Tänk på dessa egenskaper när du väljer din metod:

Kolumnlagerindexar

Tabeller med kolumnlagringsindex lagrar data i kolumnformat. Arrow-extraktion är det naturliga sättet att lämna över data för dessa tabeller eftersom data redan är kolumnorienterade i motorn. Om dina analysfrågor skannar breda tabeller med miljontals rader, ger ett icke-klustrat kolumnlagringsindex på serversidan kombinerat med Arrow-extraktion på klientsidan bästa end-to-end-genomströmning.

Indexerade vyer

Indexerade vyer förberäknar och lagrar aggregerade eller sammanfogade resultat på servern. Om din pandas- eller Polars-analys upprepade gånger beräknar samma aggregering, överväg att skapa en indexerad vy och istället söka in den vyn. Servern behåller automatiskt vyn när underliggande data ändras.

Querybutik

Query Store spårar statistik över tid för frågeexekveringar. Använd det för att identifiera vilka frågor som är tillräckligt dyra för att motivera en Arrow-extraktion och lokal DataFrame-analys jämfört med en direkt kursorläsning. Om en fråga körs på några millisekunder fungerar hämtning via markör bra. Om den skannar miljontals rader kan Arrow-extraktion och lokal analys minska serverbelastningen.

Intelligent frågebearbetning

Microsoft SQL:s intelligenta frågebehandlingsfunktioner, såsom adaptiva joins, batchläge på rowstore och minnesgivande feedback, optimerar automatiskt frågeexekveringen. Dessa funktioner fungerar oavsett vilken klientläsväg du väljer, men de gynnar stora analytiska frågor mest. Du behöver inte justera ledtrådar eller genomförandeplaner för de flesta arbetsbelastningar.

Strömma stora resultatuppsättningar

För resultatuppsättningar som inte får plats i minnet, använd strömningsmönster:

Kursorbaserad strömning med 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

Strömning baserad på Arrow till Parquet:

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

Antimönster som bör undvikas

Antimönster Problem Bättre tillvägagångssätt
fetchall() sedan pd.DataFrame() för stora tabeller Laddar in alla rader i minnet två gånger (en gång som tupler, en gång som DataFrame). Använd cursor.arrow() sedan arrow_table.to_pandas().
Att konvertera Arrow till pandas enbart för att filtrera rader Slösar minne på hela pandas-kopian. Filtrera i SQL (klausulen WHERE) eller använd Polars/DuckDB direkt på Arrow-tabellen.
SELECT * När du behöver tre kolumner Överför onödig data från servern. Lista bara de kolumner du behöver.
Bygga en DataFrame för att beräkna COUNT(*) Servern beräknar aggregerade data snabbare än Python. Använd SELECT COUNT(*) och fetchval().
Att öppna en ny anslutning per fråga Att skapa anslutningar är dyrt även med samlad överhead. Återanvänd kopplingar inom en logisk arbetsenhet.
Chaining Arrow -> pandas -> Polars Varje konvertering kopierar data. Gå direkt till ditt målformat: Arrow -> Polars eller Arrow -> pandas.