Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Il driver mssql-python fornisce oggetti cursore per l'esecuzione di query, la gestione di più set di risultati e la gestione efficiente della memoria.
Nozioni di base del cursore
Crea e usa i cursori
Chiama conn.cursor() per creare un cursore, poi usa execute() e recupera metodi per eseguire query e recuperare i risultati:
import mssql_python
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes"
)
# Create cursor
cursor = conn.cursor()
# Execute query
cursor.execute("SELECT TOP 10 Name, ListPrice FROM Production.Product")
# Process results
for row in cursor:
print(row.Name)
# Close cursor when done
cursor.close()
Schema di gestione del contesto
Usa la with dichiarazione per implementare un gestore di contesto per la pulizia automatica:
with mssql_python.connect(connection_string) as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT TOP 10 Name, ListPrice FROM Production.Product")
products = cursor.fetchall()
# Cursor automatically closed on exit
# Connection automatically closed on exit
Cursori multipli
Importante
Il driver mssql-python non supporta i Multiple Active Result Sets (MARS). Puoi creare più cursori su una singola connessione, ma solo un cursore può avere una query attiva alla volta. Recupera sempre tutti i risultati da un cursore prima di eseguire su un altro cursore sulla stessa connessione.
conn = mssql_python.connect(connection_string)
# Multiple cursors on same connection
cursor1 = conn.cursor()
cursor2 = conn.cursor()
# Fetch results completely from cursor1 before using cursor2
cursor1.execute("SELECT TOP 5 ProductID, Name FROM Production.Product")
products = cursor1.fetchall()
cursor2.execute("SELECT TOP 5 ProductCategoryID, Name FROM Production.ProductCategory")
categories = cursor2.fetchall()
cursor1.close()
cursor2.close()
Se devi eseguire query contemporaneamente, usa invece connessioni separate:
conn1 = mssql_python.connect(connection_string)
conn2 = mssql_python.connect(connection_string)
cursor1 = conn1.cursor()
cursor2 = conn2.cursor()
cursor1.execute("SELECT TOP 5 ProductID, Name FROM Production.Product")
cursor2.execute("SELECT TOP 5 ProductCategoryID, Name FROM Production.ProductCategory")
products = cursor1.fetchall()
categories = cursor2.fetchall()
cursor1.close()
cursor2.close()
conn1.close()
conn2.close()
Strategie di recupero
Recupero completo vs recupero iterativo
Usa fetchall() per caricare l'intero set di risultati in memoria contemporaneamente, oppure iterare sul cursore per elaborare le righe una alla volta senza buffering.
# Fetch all at once - loads entire result into memory
cursor.execute("SELECT * FROM Production.Product")
all_products = cursor.fetchall()
print(f"Loaded {len(all_products)} products")
# Iterative fetch - memory efficient
cursor.execute("SELECT * FROM Production.Product")
count = 0
for row in cursor:
count += 1
print(f"Processed {count} products")
Recupera in blocchi
Usa fetchmany() con una dimensione batch per elaborare grandi set di risultati in blocchi senza caricare tutto in memoria.
def process_batch(rows):
# Example: print each row. Replace with your own logic.
for row in rows:
print(row)
def fetch_in_batches(cursor, batch_size: int = 1000):
"""Fetch results in batches to manage memory."""
while True:
batch = cursor.fetchmany(batch_size)
if not batch:
break
yield batch
cursor.execute("SELECT * FROM LargeTable")
for batch in fetch_in_batches(cursor, batch_size=5000):
process_batch(batch)
print(f"Processed batch of {len(batch)} rows")
Usa fetchval per valori singoli
Usa fetchval() per query scalari che restituiscono un singolo valore. Restituisce la prima colonna della prima riga.
# Efficient for scalar queries
cursor.execute("SELECT COUNT(*) FROM Production.Product")
count = cursor.fetchval() # Returns single value directly
cursor.execute("SELECT MAX(ListPrice) FROM Production.Product")
max_price = cursor.fetchval()
Più set di risultati
Elabora più set di risultati
Usa nextset() per andare oltre il set di risultati corrente al successivo dopo aver recuperato tutte le righe del set precedente.
# Query returns multiple results
cursor.execute("""
SELECT TOP 3 CustomerID, AccountNumber FROM Sales.Customer;
SELECT TOP 3 SalesOrderID, OrderDate FROM Sales.SalesOrderHeader;
SELECT TOP 3 ProductID, Name FROM Production.Product;
""")
# First result set
print("Customers:")
customers = cursor.fetchall()
for c in customers:
print(f" {c.AccountNumber}")
# Move to second result set
if cursor.nextset():
print("Orders:")
orders = cursor.fetchall()
for o in orders:
print(f" Order #{o.SalesOrderID}")
# Move to third result set
if cursor.nextset():
print("Products:")
products = cursor.fetchall()
for p in products:
print(f" {p.Name}")
Itera tutti gli insiemi di risultati
Loop finché nextset() non ritorna False per consumare tutti i set di risultati di una singola chiamata di esecuzione:
def process_all_result_sets(cursor):
"""Process all result sets from a query."""
result_sets = []
while True:
# Fetch current result set
rows = cursor.fetchall()
result_sets.append(rows)
# Try to move to next result set
if not cursor.nextset():
break
return result_sets
cursor.execute("""
SELECT TOP 3 ProductID, Name FROM Production.Product ORDER BY ProductID;
SELECT TOP 3 SalesOrderID, TotalDue FROM Sales.SalesOrderHeader ORDER BY SalesOrderID;
""")
all_results = process_all_result_sets(cursor)
print(f"Retrieved {len(all_results)} result sets")
Controlla se esistono altri set di risultati
Controlla il valore di ritorno di nextset() in un ciclo per consumare tutti i set di risultati senza sapere in anticipo quanti sono:
cursor.execute("""
SELECT COUNT(*) AS ProductCount FROM Production.Product;
SELECT COUNT(*) AS PersonCount FROM Person.Person;
""")
result_num = 1
while True:
count = cursor.fetchval()
print(f"Result set {result_num}: {count}")
result_num += 1
if not cursor.nextset():
break
Descrizione del cursore
Metadati della colonna di accesso
Dopo aver eseguito una query, cursor.description contiene una sequenza di tuple di 7 elementi — una per colonna — con nome, codice tipo, dimensione del display, dimensione interna, precisione, scala e annullabilità:
cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 10")
# Get column information
for col in cursor.description:
print(f"Column: {col[0]}, Type: {col[1]}")
# description structure: (name, type_code, display_size, internal_size,
# precision, scale, null_ok)
Costruisci gestori dinamici di risultati
Crea gestori dei risultati in grado di funzionare con qualsiasi query costruendo l'elenco delle colonne da cursor.description in fase di esecuzione:
def query_to_dicts(cursor) -> list[dict]:
"""Convert query results to list of dictionaries."""
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 10")
products = query_to_dicts(cursor)
for p in products:
print(p["Name"])
Gestire le query senza risultati
cursor.description è None dopo istruzioni non SELECT come INSERT, UPDATE, e DELETE. Controlla prima di chiamare i metodi di recupero:
cursor.execute("CREATE TABLE #UpdDemo (Name NVARCHAR(50), Price DECIMAL(10,2), CategoryID INT)")
cursor.execute("INSERT INTO #UpdDemo VALUES ('Widget', 10.0, 5), ('Gadget', 20.0, 5)")
cursor.execute("UPDATE #UpdDemo SET Price = Price * 1.1 WHERE CategoryID = 5")
# description is None for non-SELECT statements
if cursor.description is None:
print(f"Updated {cursor.rowcount} rows")
else:
results = cursor.fetchall()
Numero di righe
Tieni traccia delle righe interessate
Dopo INSERT, UPDATE, o DELETE, cursor.rowcount restituisce il numero di righe influenzate dall'affermazione:
cursor.execute("CREATE TABLE #RowDemo (Name NVARCHAR(50), Stock INT)")
cursor.execute("INSERT INTO #RowDemo VALUES ('A', 0), ('B', 5), ('C', 0)")
cursor.execute("UPDATE #RowDemo SET Stock = -1 WHERE Stock = 0")
print(f"Rows affected: {cursor.rowcount}")
cursor.execute("DELETE FROM #RowDemo WHERE Stock = -1")
print(f"Deleted {cursor.rowcount} rows")
Gestione del numero di righe sconosciuto
# Some operations might not return row count
cursor.execute("EXEC dbo.uspGetEmployeeManagers @BusinessEntityID = 5")
if cursor.rowcount == -1:
print("Row count not available")
else:
print(f"Affected {cursor.rowcount} rows")
Ignora righe
Usa skip per l'alternativa alla paginazione
cursor.skip() avanza la posizione del cursore senza recuperare le righe. Per grandi dataset, preferisci la paginazione OFFSET-FETCH a livello SQL per migliori prestazioni:
def get_page_using_skip(cursor, page: int, page_size: int):
"""Get a page of results using skip."""
cursor.execute("SELECT * FROM Production.Product ORDER BY ProductID")
# Skip rows from previous pages
cursor.skip((page - 1) * page_size)
# Fetch this page
return cursor.fetchmany(page_size)
# Get page 3
page_3 = get_page_using_skip(cursor, page=3, page_size=20)
Note
Per set di dati di grandi dimensioni, usa la paginazione a livello SQL (OFFSET-FETCH) invece dello skip eseguito lato client, poiché è più efficiente.
Messaggi diagnostici
Accesso cursor.messaggi
L'attributo messages memorizza messaggi informativi generati durante l'esecuzione delle istruzioni SQL, come descritto in PEP 249. Questi messaggi includono l'output delle istruzioni PRINT e i messaggi RAISERROR con livelli di gravità inferiori a 11.
L'attributo è un elenco di tuple in cui ogni tupla contiene un codice di tipo di messaggio e il testo del messaggio:
conn = mssql_python.connect(connection_string, autocommit=True)
cursor = conn.cursor()
cursor.execute("PRINT 'Hello world!'")
print(cursor.messages)
Output:
[('[01000] (0)', '[Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Hello world!')]
Il testo del messaggio include informazioni sul prefisso del driver perché il driver recupera i messaggi come record diagnostici tramite SQLGetDiagRec.
Acquisire messaggi da procedure memorizzate
Leggi cursor.messages dopo l'esecuzione per ottenere qualsiasi output PRINT o messaggio informativo del server dall'istruzione precedente:
cursor.execute("EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = 5")
results = cursor.fetchall()
# Check for any informational messages
if cursor.messages:
for msg_type, msg_text in cursor.messages:
print(f"Server message: {msg_text}")
Gestione della memoria
Elaborare i grandi risultati in modo efficiente
Recupera in batch utilizzando fetchmany() per elaborare tabelle troppo grandi per essere caricate in memoria in una sola volta:
def process_large_table(cursor, batch_size: int = 10000):
"""Process large result set without loading all into memory."""
cursor.execute("SELECT * FROM VeryLargeTable")
total_processed = 0
while True:
rows = cursor.fetchmany(batch_size)
if not rows:
break
for row in rows:
process_row(row)
total_processed += len(rows)
print(f"Progress: {total_processed} rows processed")
return total_processed
Elaborazione basata su generatori
Incapsula il batch fetching in un generatore per elaborare una riga alla volta mantenendo costante l'uso della memoria indipendentemente dalla dimensione del set di risultati:
def row_generator(cursor, batch_size: int = 1000):
"""Generate rows from cursor without loading all."""
while True:
rows = cursor.fetchmany(batch_size)
if not rows:
break
for row in rows:
yield row
cursor.execute("SELECT * FROM LargeTable")
for row in row_generator(cursor, batch_size=5000):
# Process one row at a time
print(row) # Replace with your own row-handling logic
Chiudi i cursori immediatamente
Chiudi sempre i cursori in un finally blocco per liberare risorse lato server anche se si verifica un'eccezione:
def get_product(conn, product_id: int):
"""Get product and properly close cursor."""
cursor = conn.cursor()
try:
cursor.execute(
"SELECT * FROM Production.Product WHERE ProductID = %(id)s",
{"id": product_id}
)
return cursor.fetchone()
finally:
cursor.close()
Gestione dello stato del cursore
Controlla se il cursore contiene dati
Verifica se una query restituisce righe controllando se fetchone() restituisce None:
cursor.execute("SELECT ProductID, Name FROM Production.Product WHERE ProductID = 999")
row = cursor.fetchone()
if row is None:
print("Product not found")
else:
print(f"Found: {row.Name}")
Riutilizza i cursori
Un singolo cursore può eseguire più query in sequenza. Ogni execute() chiamata sostituisce il precedente set di risultati:
cursor = conn.cursor()
# Execute multiple queries with same cursor
cursor.execute("SELECT TOP 5 * FROM Sales.Customer")
customers = cursor.fetchall()
cursor.execute("SELECT TOP 5 * FROM Production.Product")
products = cursor.fetchall()
cursor.execute("SELECT TOP 5 * FROM Sales.SalesOrderHeader")
orders = cursor.fetchall()
cursor.close()
Procedure consigliate
Modello: Classe aiutante cursore
Racchiudi la gestione del ciclo di vita del cursore in una classe helper per ridurre il boilerplate nella tua applicazione:
class CursorManager:
"""Helper for managing cursor lifecycle."""
def __init__(self, connection):
self.conn = connection
def execute_and_fetch(self, query: str, params: dict = None) -> list:
"""Execute query and return all results."""
cursor = self.conn.cursor()
try:
cursor.execute(query, params or {})
return cursor.fetchall()
finally:
cursor.close()
def execute_scalar(self, query: str, params: dict = None):
"""Execute query and return single value."""
cursor = self.conn.cursor()
try:
cursor.execute(query, params or {})
return cursor.fetchval()
finally:
cursor.close()
def execute_non_query(self, query: str, params: dict = None) -> int:
"""Execute non-SELECT and return row count."""
cursor = self.conn.cursor()
try:
cursor.execute(query, params or {})
return cursor.rowcount
finally:
cursor.close()
# Usage
db = CursorManager(conn)
products = db.execute_and_fetch("SELECT TOP 5 Name FROM Production.Product")
count = db.execute_scalar("SELECT COUNT(*) FROM Production.Product")
db.execute_non_query("CREATE TABLE #Logs (LogID INT, Age INT)")
db.execute_non_query("INSERT INTO #Logs VALUES (1, 45), (2, 20), (3, 60)")
affected = db.execute_non_query("DELETE FROM #Logs WHERE Age > 30")
Non lasciare i cursori aperti
Un cursore che non è esplicitamente chiuso trattiene le risorse lato server fino alla chiusura della connessione. Usa try/finally per garantire la pulizia:
# Bad: cursor left open
def get_data_bad(conn):
cursor = conn.cursor()
cursor.execute("SELECT * FROM Data")
return cursor.fetchall()
# Cursor never closed!
# Good: always close cursor
def get_data_good(conn):
cursor = conn.cursor()
try:
cursor.execute("SELECT * FROM Data")
return cursor.fetchall()
finally:
cursor.close()
Associa la durata del cursore all'operazione
Crea cursori di breve durata per singole operazioni. Riutilizzare lo stesso cursore solo per una sequenza di operazioni correlate:
# Short-lived cursor for simple query
def get_user_count(conn) -> int:
cursor = conn.cursor()
try:
cursor.execute("SELECT COUNT(*) FROM Person.Person")
return cursor.fetchval()
finally:
cursor.close()
# Reuse cursor for related operations
def update_inventory(conn, items: list):
cursor = conn.cursor()
try:
for item in items:
cursor.execute(
"UPDATE Inventory SET Quantity = %(qty)s WHERE ProductID = %(id)s",
item
)
conn.commit()
finally:
cursor.close()