Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Der Treiber mssql-python bietet mehrere Pfade zum Lesen von Daten aus Microsoft SQL. Jeder Weg passt zu unterschiedlichen Arbeitslasten. Dieser Leitfaden hilft Ihnen, basierend auf Ihrer Datengröße, Ihren Analysebedürfnissen und Ihren Leistungsanforderungen das richtige Modell auszuwählen.
Entscheide nach Arbeitsbelastung
Verwenden Sie diese Tabelle, um Ihren Ausgangspunkt zu finden:
| Arbeitsbelastung | Empfohlener Pfad | Warum? |
|---|---|---|
| Anwendungszeilenzugriff (Web-API, CRUD) | Methoden zum Abrufen von Cursorn | Geringer Overhead, zeilenweise Verarbeitung, keine zusätzlichen Abhängigkeiten. |
| Kleine bis mittlere Berichtsanfragen | pandas | Vertraute API zum Filtern, Gruppieren und Visualisieren. |
| Große Ergebnissätze oder breite Tabellen | Pfeilextraktion | Null-Kopie-Spaltenübertragung, minimaler Speicheraufwand. |
| Hochleistungsanalysen | Polars mit Pfeil | Multithreading-Ausführung auf spaltenorientierten Daten, keine GIL-Kontention. |
| Ad-hoc-SQL über lokale und entfernte Daten | DuckDB mit Pfeil | SQL-Analysen auf Arrow-Tabellen, Verknüpfung mit lokalen CSV-/Parquet-Dateien. |
| Notebook-Erkundung | Pandas oder Polare mit Pfeil | Wählen Sie basierend auf Teamkenntnis und Datengröße. |
Methoden zum Abrufen von Cursorn
Nutze Standard-Cursormethoden, wenn du einen zeilenorientierten Zugriff ohne zusätzliche Abhängigkeiten brauchst. Diese Methode ist die richtige Wahl für Anwendungscode, der jeweils eine Zeile verarbeitet, API-Antworten zurückgibt oder die Anwendungslogik mit Daten versorgt.
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()
Verwendung fetchmany() für speichereffiziente Batch-Verarbeitung großer Ergebnismengen. Verwenden Sie fetchval(), wenn Sie einen einzelnen Wert wie eine Anzahl, ein Maximum oder eine Existenzprüfung benötigen.
Für vollständige Fetch-Methoden-Dokumentation siehe Daten abrufen.
Pfeilextraktion
Verwenden Sie Arrow-Extraktion, wenn Sie Kolumnendaten für Analysen, DataFrame-Aufbau oder den Export nach Parquet benötigen. Arrow ermöglicht eine Zero-Copy-Datenübertragung vom Treiber, wodurch der Zeilen-für-Zeilen-Umwandlungsaufwand beim Erstellen eines DataFrame aus fetchall()vermieden wird.
Tabellen mit Spaltenspeicher-Indizes sind bereits im Spaltenformat in der Datenbank-Engine gespeichert, was die Arrow-Extraktion zu einer natürlichen Lösung für diese Workloads macht.
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")
Verwenden Sie bei großen Ergebnismengen arrow_reader(), um in Stapeln zu streamen, ohne alles in den Speicher zu laden:
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")
Pfeiltabellen sind der Ausgangspunkt für Pandas, Polars und DuckDB. Einmal extrahieren und dann umwandeln:
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 die vollständige Arrow-Dokumentation siehe Apache Arrow Integration.
pandas
Nutzen Sie Pandas, wenn Sie eine vertraute DataFrame-API für Berichterstattung, Ad-hoc-Analyse oder Datenbereinigung benötigen. Pandas funktioniert am besten mit Ergebnismengen, die im Speicher passen (bis zu ein paar Millionen Zeilen, je nach Spaltenbreite).
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 größere Ergebnissätze bauen Sie den DataFrame aus Arrow anstelle von fetchall():
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
arrow_table = cursor.arrow()
df = arrow_table.to_pandas()
Für vollständige Pandas-Muster einschließlich ETL, Zeitreihen und Write-Back siehe Pandas-Integration.
Polars mit Pfeil
Verwenden Sie Polars, wenn Sie schnellere DataFrame-Operationen bei größeren Ergebnismengen benötigen. Polars verwendet Apache Arrow als Speicherformat, sodass die Übertragung von cursor.arrow() ohne Kopieren erfolgt. Polars führt außerdem Operationen auf mehreren Threads aus, was GIL-Konkurrenz bei CPU-lastigen Transformationen vermeidet.
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 das Streaming großer Ergebnissätze:
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 vollständige Polars-Muster siehe Polars-Integration.
DuckDB mit Pfeil
Verwenden Sie DuckDB, wenn Sie SQL-Analysen auf extrahierten Daten durchführen, Serverdaten mit lokalen CSV- oder Parquet-Dateien verbinden oder Ergebnisse in Dateiformate exportieren müssen. DuckDB arbeitet auf Arrow-Tabellen mit Null-Kopierzugriff.
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())
Serverdaten mit einer lokalen Datei verknüpfen:
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
""")
Export nach Parquet:
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
orders = cursor.arrow()
duckdb.sql("COPY orders TO 'orders.parquet' (FORMAT PARQUET)")
Für vollständige DuckDB-Muster siehe DuckDB-Integration.
Microsoft SQL-Funktionen, die die Lesepfadentscheidungen beeinflussen
Die Datenbank-Engine verfügt über Funktionen, die direkt beeinflussen, welcher Lesepfad am besten funktioniert. Berücksichtigen Sie diese Merkmale bei der Wahl Ihres Ansatzes:
ColumnStore-Indizes
Tabellen mit Spaltenspeicher-Indizes speichern Daten im Spaltenformat. Die Arrow-Extraktion ist die naheliegende Übergabe für diese Tabellen, da die Daten in der Engine bereits spaltenorientiert vorliegen. Wenn Ihre Analytics-Abfragen breite Tabellen mit Millionen von Zeilen scannen, sorgt ein nicht-geclusterter Columnstore-Index auf Serverseite kombiniert mit Arrow-Extraktion auf der Client-Seite für den besten End-to-End-Durchsatz.
Indizierte Ansichten
Indexierte Ansichten berechnen und speichern aggregierte oder zusammengefügte Ergebnisse auf dem Server. Wenn deine Pandas- oder Polars-Analyse wiederholt dieselbe Aggregation berechnet, solltest du stattdessen eine indexierte Ansicht erstellen und diese Ansicht abfragen. Der Server hält die Ansicht automatisch aufrecht, wenn sich die zugrunde liegenden Daten ändern.
Abfragespeicher
Abfragespeicher verfolgt Abfrageausführungsstatistiken im Zeitverlauf. Nutzen Sie es, um zu identifizieren, welche Abfragen teuer genug sind, um eine Arrow-Extraktion und lokale DataFrame-Analyse im Vergleich zu einem direkten Cursor-Lesen zu rechtfertigen. Wenn eine Abfrage innerhalb von Millisekunden läuft, ist der Cursor-Fetch in Ordnung. Wenn es Millionen von Zeilen scannt, könnten Arrow-Extraktion und lokale Analyse die Serverbelastung verringern.
Intelligente Abfrageverarbeitung
Die intelligenten Abfrageverarbeitungsfunktionen von Microsoft SQL, wie adaptive Joins, Batch-Modus im Rowstore und Memory Grant Feedback, optimieren die Abfrageausführung automatisch. Diese Funktionen funktionieren unabhängig davon, welchen Kundenlesepfad Sie wählen, aber sie kommen großen analytischen Abfragen am meisten zugute. Du musst keine Hinweise oder Ausführungspläne für die meisten Workloads optimieren.
Große Ergebnismengen streamen
Für Ergebnismengen, die nicht ins Gedächtnis passen, verwenden Sie Streaming-Muster:
Cursorbasiertes Streaming mit 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
Pfeilbasiertes Streaming zu 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()
Anti-Muster, die man vermeiden sollte
| Anti-Muster | Problem | Besserer Ansatz |
|---|---|---|
fetchall() dann gilt pd.DataFrame() für große Tabellen |
Lädt alle Zeilen zweimal in den Speicher (einmal als Tupel, einmal als DataFrame). | Verwenden Sie cursor.arrow() und dann arrow_table.to_pandas(). |
| Arrow in Pandas umwandeln, nur um Zeilen zu filtern | Verschwendet Speicher für die vollständige pandas-Kopie. | Filtern Sie in SQL (WHERE-Klausel) oder verwenden Sie Polars/DuckDB direkt auf der Arrow-Tabelle. |
SELECT * Wenn du drei Spalten brauchst |
Überträgt unnötige Daten vom Server. | Listen Sie nur die Spalten auf, die Sie benötigen. |
Aufbau eines DataFrame zur Berechnung COUNT(*) |
Der Server berechnet Aggregate schneller als Python. | Verwenden Sie SELECT COUNT(*) und fetchval(). |
| Eröffnung einer neuen Verbindung pro Abfrage | Das Erstellen einer Verbindung ist selbst unter Berücksichtigung des Overheads durch das Pooling teuer. | Wiederverwenden Sie Verbindungen innerhalb einer logischen Arbeitseinheit. |
| Kettenpfeil –> Pandas –> Polare | Jede Konvertierung kopiert Daten. | Wechsle direkt zu deinem Zielformat: Arrow -> Polars oder Arrow -> pandas. |