Usa mssql-python con DuckDB

DuckDB è un motore di analisi SQL in processo che può interrogare direttamente le tabelle Apache Arrow senza dover copiare dati. Combinando DuckDB con il driver mssql-python puoi farti:

  • Esegui query SQL analitiche su set di risultati di Microsoft SQL senza caricare dati in pandas o Polars.
  • Interroga le tabelle Arrow in memoria senza overhead di copia.
  • Unisci i dati SQL Microsoft ai file locali (CSV, Parquet, JSON) in una singola query DuckDB.
  • Esporta i dati Microsoft SQL su Parquet, CSV o altri formati tramite DuckDB.

Prerequisiti

  • Python 3.10 o versione successiva.
  • I pacchetti mssql-python, duckdb e pyarrow. Installa tutto con pip install mssql-python duckdb pyarrow.
  • Installare prerequisiti specifici del sistema operativo monouso. Gli utenti Windows possono saltare questo passaggio. Per dettagli completi sulla piattaforma, vedi Installa mssql-python.
    apk add libtool krb5-libs krb5-dev
    

Creare un database SQL

Crea o collegati a un database SQL su una delle seguenti piattaforme:

Gli esempi in questo articolo interrogano il AdventureWorks database di esempio. Se non lo possiedi già, consulta i database di esempio di AdventureWorks.

Installa le dipendenze

pip install mssql-python duckdb pyarrow

Consulta i dati SQL di Microsoft con DuckDB

Il flusso di lavoro base è: eseguire una query con mssql-python, recuperare i risultati come tabella delle frecce, poi interrogare quella tabella con DuckDB SQL.

Schema di base

Inizia stabilendo una connessione e recuperando i dati come tabella Arrow.

import duckdb
import mssql_python

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes"
)
cursor = conn.cursor()
# Fetch Microsoft SQL data as Arrow
cursor.execute("SELECT * FROM Production.Product WHERE ListPrice > 0")
products = cursor.arrow()

# Query the Arrow table with DuckDB
result = duckdb.sql("""
    SELECT Color, COUNT(*) AS ProductCount, AVG(ListPrice) AS AvgPrice
    FROM products
    GROUP BY Color
    ORDER BY ProductCount DESC
""")
print(result.fetchdf())

DuckDB fa riferimento alla products tabella Arrow con il nome della sua variabile Python. Nessun dato viene copiato nello storage di DuckDB.

Aggregato e filtro

Usa l'SQL di DuckDB per raggruppare e aggregare i dati Arrow.

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

# Top customers by total spend
top_customers = duckdb.sql("""
    SELECT
        CustomerID,
        COUNT(*) AS OrderCount,
        SUM(TotalDue) AS TotalSpent,
        AVG(TotalDue) AS AvgOrderValue
    FROM orders
    GROUP BY CustomerID
    HAVING SUM(TotalDue) > 10000
    ORDER BY TotalSpent DESC
    LIMIT 20
""")
print(top_customers.fetchdf())

Unire più risultati di Microsoft SQL

Prendi più tabelle da Microsoft SQL e uniscile in DuckDB senza scrivere una query cross-server.

# Fetch two tables
cursor.execute("SELECT * FROM Production.Product")
products = cursor.arrow()

cursor.execute("SELECT * FROM Production.ProductSubcategory")
subcategories = cursor.arrow()

# Join in DuckDB
result = duckdb.sql("""
    SELECT
        s.Name AS Subcategory,
        COUNT(*) AS ProductCount,
        ROUND(AVG(p.ListPrice), 2) AS AvgPrice
    FROM products p
    JOIN subcategories s ON p.ProductSubcategoryID = s.ProductSubcategoryID
    GROUP BY s.Name
    ORDER BY AvgPrice DESC
""")
print(result.fetchdf())

Unisci i dati SQL di Microsoft con file locali

DuckDB può leggere file CSV, Parquet e JSON nativamente. Combina i dati di SQL Server con i file locali in un'unica query.

Unisci con un file CSV

Carica un file CSV e uniscilo con i dati di Microsoft SQL.

import csv
from pathlib import Path

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

csv_path = Path("customer_regions.csv")
with csv_path.open("w", newline="", encoding="utf-8") as file:
    writer = csv.writer(file)
    writer.writerow(["CustomerID", "Region", "Segment"])
    writer.writerows([
        (1, "West", "Premium"),
        (2, "East", "Standard"),
        (3, "Central", "Basic"),
    ])

try:
    result = duckdb.sql("""
        SELECT c.CustomerID, c.PersonID, f.Region, f.Segment
        FROM customers c
        JOIN read_csv_auto('customer_regions.csv') f ON c.CustomerID = f.CustomerID
    """)
    print(result.fetchdf())
finally:
    csv_path.unlink(missing_ok=True)

Unisci a un file Parquet

Carica un file Parquet e uniscilo ai dati di Microsoft SQL.

from pathlib import Path

import pyarrow as pa
import pyarrow.parquet as pq

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product")
products = cursor.arrow()

parquet_path = Path("order_history.parquet")
order_history = pa.table({
    "ProductID": [1, 2, 680],
    "OrderDate": ["2024-06-01", "2024-03-15", "2024-01-10"],
    "Quantity": [10, 5, 3],
})
pq.write_table(order_history, parquet_path)

try:
    result = duckdb.sql("""
        SELECT p.Name, p.ListPrice, h.OrderDate, h.Quantity
        FROM products p
        JOIN read_parquet('order_history.parquet') h ON p.ProductID = h.ProductID
        WHERE h.OrderDate >= '2024-01-01'
    """)
    print(result.fetchdf())
finally:
    parquet_path.unlink(missing_ok=True)

Esporta dati di Microsoft SQL

Usa la dichiarazione di COPY DuckDB per esportare i dati Microsoft SQL in vari formati file.

Esportazione in parquet

Esporta i dati in formato Apache Parquet.

cursor.execute("SELECT * FROM Production.Product")
products = cursor.arrow()

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

Esporta in CSV

Esporta i dati in un file di valori separati da virgole:

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

duckdb.sql("COPY orders TO 'orders.csv' (FORMAT CSV, HEADER)")

Esporta Parquet partizionato

Esporta i dati in file Parquet partizionati per l'analisi distribuita:

import shutil
from pathlib import Path

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

output_dir = Path("sales_data")
shutil.rmtree(output_dir, ignore_errors=True)

duckdb.sql("""
    COPY (SELECT *, YEAR(OrderDate) AS OrderYear FROM orders)
    TO 'sales_data'
    (FORMAT PARQUET, PARTITION_BY (OrderYear))
""")

Trasmettere in streaming set di risultati di grandi dimensioni

Per grandi dataset, si usa arrow_reader() per elaborare dati in batch in streaming senza caricare tutte le righe in memoria contemporaneamente:

cursor.execute("SELECT * FROM Production.TransactionHistory")
reader = cursor.arrow_reader(batch_size=50000)

# Process each batch with DuckDB
total_rows = 0
for batch in reader:
    result = duckdb.sql("""
        SELECT ProductID, SUM(ActualCost) AS TotalCost
        FROM batch
        GROUP BY ProductID
    """)
    total_rows += batch.num_rows
    print(f"Processed {total_rows} rows")

Accumula i risultati dello streaming

Per aggregare su tutti i lotti, registrare ogni batch in una connessione DuckDB persistente e accumulare i risultati in modo incrementale.

cursor.execute("SELECT * FROM Production.TransactionHistory")
reader = cursor.arrow_reader(batch_size=50000)

duck = duckdb.connect()
duck.execute("CREATE TABLE transactions (ProductID INT, ActualCost DOUBLE, Quantity INT)")

for batch in reader:
    duck.execute("INSERT INTO transactions SELECT ProductID, ActualCost, Quantity FROM batch")

# Query the accumulated data
result = duck.sql("""
    SELECT ProductID, SUM(ActualCost) AS TotalCost, SUM(Quantity) AS TotalQty
    FROM transactions
    GROUP BY ProductID
    ORDER BY TotalCost DESC
    LIMIT 10
""")
print(result.fetchdf())
duck.close()

Suggerimenti per le prestazioni

Lascia che Microsoft SQL si occupi del lavoro pesante

Microsoft SQL è più veloce per filtri, join e aggregazioni che per estrarre tutti i dati grezzi via wire. Usa DuckDB per l'analisi secondaria su set di risultati già recuperati, non come sostituto dell'ottimizzazione delle query di SQL Server.

# Suboptimal: Pull all rows, filter in DuckDB
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
orders = cursor.arrow()
result = duckdb.sql("SELECT * FROM orders WHERE TotalDue > 1000")

# Better: Filter in Microsoft SQL, analyze in DuckDB
cursor.execute("SELECT * FROM Sales.SalesOrderHeader WHERE TotalDue > 1000")
orders = cursor.arrow()
result = duckdb.sql("SELECT CustomerID, SUM(TotalDue) FROM orders GROUP BY CustomerID")

Usa Arrow per tutte le operazioni di lettura

Il trasferimento basato su frecce evita la creazione di oggetti Python intermedi, riducendo l'uso di memoria e migliorando la produttività. Preferisci cursor.arrow() alla conversione manuale riga per riga quando si passano dati a DuckDB.

Usa lo streaming per i grandi dataset

Per set di risultati più grandi della memoria disponibile, utilizzare arrow_reader() con un batch_size parametro per elaborare i dati in modo incrementale.