Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
mssql-python-drivrutinen implementerar callproc() inte metoden från DB-API 2.0-specifikationen. Att anropa callproc() utlöser NotSupportedError. Använd istället ODBC {CALL ...} :s escape-sekvens med standardmetoder för frågeexekvering.
Grundläggande körning av lagrade procedurer
Utan parametrar
Kör en lagrad procedur genom att använda {CALL} escapesekvensen. Detta exempel anropar sp_databases systemlagred procedur, som inte tar några parametrar och returnerar en rad per databas:
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("{CALL sp_databases}")
for row in cursor:
print(row.DATABASE_NAME, row.DATABASE_SIZE)
Med indataparametrar
Passparametrar med positions- eller namngivna platshållare:
# Single parameter
cursor.execute(
"{CALL dbo.uspGetManagerEmployees(?)}", (16,)
)
for row in cursor:
print(row.FirstName, row.LastName)
Utdataparametrar
Deklarera och hämta utgångsparametrar
SQL Server lagrade procedurer kan returnera värden via utdataparametrar. Använd Transact-SQL-variabler (T-SQL) för att fånga upp utdatavärden och hämta dem därefter med en SELECT-sats:
cursor.execute("""
DECLARE @total_out MONEY;
SELECT @total_out = SUM(TotalDue)
FROM Sales.SalesOrderHeader
WHERE CustomerID = %(customer_id)s;
SELECT @total_out AS TotalAmount;
""", {"customer_id": 29825})
row = cursor.fetchone()
total = row.TotalAmount
print(f"Customer total: ${total}")
Flera utgångsparametrar
Fånga flera utdatavärden genom att deklarera separata variabler. Samma mönster fungerar med alla lagrade procedur som har OUTPUT parametrar:
cursor.execute("""
DECLARE @total_orders INT, @total_spent MONEY;
SELECT @total_orders = COUNT(*), @total_spent = SUM(TotalDue)
FROM Sales.SalesOrderHeader
WHERE CustomerID = %(cust_id)s;
SELECT @total_orders AS OrderCount, @total_spent AS TotalSpent;
""", {"cust_id": 29825})
stats = cursor.fetchone()
print(f"Orders: {stats.OrderCount}, Total spent: ${stats.TotalSpent}")
Returnera värden
Fånga returvärdet från en lagrad procedur
Kör en lagrad procure som returnerar resultat:
cursor.execute("EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = %(emp_id)s", {"emp_id": 5})
rows = cursor.fetchall()
if rows:
print(f"Found {len(rows)} managers in chain")
for row in rows:
print(f" Manager: {row.FirstName} {row.LastName}")
else:
print("No managers found")
Returvärde med utdataparametrar
cursor.execute("""
DECLARE @return_value INT, @message NVARCHAR(500);
SELECT @return_value = CASE WHEN COUNT(*) > 0 THEN 0 ELSE 1 END,
@message = CASE WHEN COUNT(*) > 0 THEN N'Customer found' ELSE N'Customer not found' END
FROM Sales.Customer WHERE CustomerID = %(cust_id)s;
SELECT @return_value AS ReturnCode, @message AS Message;
""", {"cust_id": 29825})
result = cursor.fetchone()
print(f"Return code: {result.ReturnCode}, Message: {result.Message}")
Resultatuppsättningar
En resultatmängd
Kör en lagrad procedur som returnerar en enda resultatuppsättning:
cursor.execute(
"{CALL dbo.uspGetBillOfMaterials(?, ?)}",
(800, "2026-01-01")
)
customers = cursor.fetchall()
for row in customers:
print(f"{row.ProductAssemblyID}: {row.ComponentDesc}")
Flera resultatuppsättningar
Vissa lagrade procedurer returnerar flera resultatuppsättningar. Använd nextset() för att navigera mellan dem:
cursor.execute("""
SELECT TOP 1 SalesOrderID, OrderDate, TotalDue
FROM Sales.SalesOrderHeader WHERE CustomerID = 29825;
SELECT TOP 3 Name, ListPrice
FROM Production.Product WHERE ListPrice > 0
ORDER BY ListPrice DESC;
""")
# First result set: order header
order = cursor.fetchone()
print(f"Order: {order.SalesOrderID}, Date: {order.OrderDate}")
cursor.nextset()
# Second result set: products
print("Products:")
for item in cursor:
print(f" {item.Name}: ${item.ListPrice}")
Kontrollera om det finns fler resultatuppsättningar
Iterera igenom alla resultatuppsättningar som returneras av en lagrad procedur med hjälp av nextset():
cursor.execute("SELECT TOP 3 ProductID, Name FROM Production.Product; SELECT TOP 3 FirstName, LastName FROM Person.Person")
result_set_num = 1
while True:
print(f"--- Result Set {result_set_num} ---")
for row in cursor:
print(row)
if not cursor.nextset():
break
result_set_num += 1
Transaktioner med lagrade procedurer
Uttrycklig transaktionskontroll
Slå in flera anrop med lagrade procedurer i en transaktion för att säkerställa atomicitet:
conn.autocommit = False
try:
cursor.execute("{CALL dbo.DebitAccount(?, ?)}", (1001, 100.00))
cursor.execute("{CALL dbo.CreditAccount(?, ?)}", (1002, 100.00))
conn.commit()
print("Transfer completed")
except mssql_python.DatabaseError as e:
conn.rollback()
print(f"Transfer failed: {e}")
Låt lagrad procedur hantera transaktionen
Om den lagrade proceduren hanterar sina egna transaktioner:
conn.autocommit = True # Let SP manage transactions
cursor.execute("""
DECLARE @result INT;
EXECUTE @result = dbo.TransferFunds
@FromAccount = %(from_acc)s,
@ToAccount = %(to_acc)s,
@Amount = %(amount)s;
SELECT @result AS TransferResult;
""", {"from_acc": 1001, "to_acc": 1002, "amount": 100.00})
result = cursor.fetchone()
if result.TransferResult == 0:
print("Transfer successful")
Felhantering
Fånga fel i lagrad procedur
Hantera undantag som väcks av lagrade procedurer eller Transact-SQL-satser:
try:
cursor.execute("{CALL dbo.DangerousProcedure}")
except mssql_python.ProgrammingError as e:
# Handle SQL errors raised by RAISERROR or THROW
print(f"Stored procedure error: {e}")
except mssql_python.DatabaseError as e:
# Handle other database errors
print(f"Database error: {e}")
Fånga PRINT-uttalanden och informationsmeddelanden
SQL Server-instruktioner PRINT och RAISERROR med allvarlighetsgrad under 11 fångas upp i cursor.messages efter körning. Varje post är en (message_type, message_text)-tupel. När en PRINT körs före en resultatuppsättning utgör den en egen resultatuppsättning utan rader, så läs först cursor.messages och anropa sedan nextset() för att nå raderna:
cursor.execute("PRINT 'Operation complete'; SELECT 1 AS Status")
for msg_type, msg_text in cursor.messages:
print(f"Server message: {msg_text}")
cursor.nextset()
row = cursor.fetchone()
print(f"Status: {row.Status}")
När en lagrad procedur sänder PRINT-meddelanden i flera resultatmängder, läs cursor.messages efter execute() och igen efter varje nextset()-anrop så att meddelanden från varje resultatmängd fångas:
cursor.execute("""
PRINT 'Starting first result set';
SELECT TOP 3 ProductID, Name FROM Production.Product;
PRINT 'Starting second result set';
SELECT TOP 3 FirstName, LastName FROM Person.Person;
""")
all_messages = []
while True:
all_messages.extend(cursor.messages)
if cursor.description:
for row in cursor:
print(row)
if not cursor.nextset():
break
for _, text in all_messages:
print(f"Server: {text}")
För hela cursor.messages API:et, se Cursor management.
Metodtips
Använd namngivna parametrar
Namngivna parametrar är tydligare och behåller oberoendet av ordningen:
# Recommended: {CALL} with positional parameters
cursor.execute(
"{CALL dbo.uspGetBillOfMaterials(?, ?)}",
(800, "2026-01-01")
)
# Also valid: EXECUTE with named T-SQL parameters
cursor.execute("""
EXECUTE dbo.uspGetBillOfMaterials
@StartProductID = ?,
@CheckDate = ?
""", (800, "2026-01-01"))
Hantera nullbara utdataparametrar
Kontrollera om det finns NULL-värden när du hämtar utdataparametrar från lagrade procedurer:
cursor.execute("""
DECLARE @optional_value NVARCHAR(100);
SELECT @optional_value = Color FROM Production.Product WHERE ProductID = %(id)s;
SELECT @optional_value AS OutputValue;
""", {"id": 1})
result = cursor.fetchone()
if result.OutputValue is not None:
print(f"Value: {result.OutputValue}")
else:
print("No value returned")
Använd SET NOCOUNT ON i lagrade procedurer
För bättre prestanda och renare resultathantering, se till att dina lagrade procedurer inkluderar:
CREATE PROCEDURE dbo.MyProcedure
AS
BEGIN
SET NOCOUNT ON; -- Prevents "n rows affected" messages
-- procedure logic
END
Exempel: Komplett arbetsflöde
Här är ett praktiskt exempel som anropar en lagrad procedur, hämtar utdata och hanterar fel:
import mssql_python
def get_employee_report(manager_id: int) -> dict:
"""Look up a manager's employees and compute average vacation hours."""
conn = mssql_python.connect(connection_string)
conn.autocommit = False
cursor = conn.cursor()
try:
# Get manager info
cursor.execute("""
SELECT BusinessEntityID, JobTitle
FROM HumanResources.Employee
WHERE BusinessEntityID = %(mgr)s
""", {"mgr": manager_id})
mgr = cursor.fetchone()
print(f"Manager {mgr.BusinessEntityID}: {mgr.JobTitle}")
# Get direct reports via stored procedure
cursor.execute("{CALL dbo.uspGetManagerEmployees(?)}", (manager_id,))
employees = cursor.fetchall()
print(f"Found {len(employees)} employee(s)")
# Compute average vacation hours
cursor.execute("""
DECLARE @avg_hours INT;
SELECT @avg_hours = AVG(VacationHours)
FROM HumanResources.Employee;
SELECT @avg_hours AS AvgVacation;
""")
avg = cursor.fetchone().AvgVacation
print(f"Avg vacation hours: {avg}")
conn.commit()
return {"manager": mgr.JobTitle, "reports": len(employees), "avg_vacation": avg}
except mssql_python.DatabaseError as e:
conn.rollback()
raise
finally:
cursor.close()
conn.close()
# Usage
result = get_employee_report(manager_id=16)
Få genererade nycklar genom att använda OUTPUT INSERTED
För att hämta ett identitetsvärde från en INSERT (med eller utan en lagrad procedur), använd OUTPUT INSERTED istället för SCOPE_IDENTITY(). Denna metod returnerar värdet i samma resultatmängd:
cursor.execute("""
INSERT INTO Production.ProductCategory (Name)
OUTPUT INSERTED.ProductCategoryID
VALUES (%(name)s)
""", {"name": "Custom Parts"})
new_id = cursor.fetchval()
print(f"New category ID: {new_id}")
Detta mönster fungerar för alla tabeller med en identitetskolumn och kräver ingen lagrad procedur.
Funktioner som inte stöds
callproc()
Föraren mssql-python höjer NotSupportedError om du ringer cursor.callproc(). Använd cursor.execute("{CALL ...}") eller cursor.execute("EXECUTE ...") istället, som visas genom hela denna artikel.
Tabellvärda parametrar (TVP)
Tabellvärda parametrar stöds inte i den nuvarande versionen (1.11.0) av mssql-python. Om du behöver skicka en uppsättning rader till en lagrad procedur, använd alternativ:
- Sätt in i en temptabell först, och låt sedan den lagrade proceduren läsas från den.
- Använd
bulkcopy()för att ladda data i en staging-tabell. - Skicka en JSON-sträng och parsa den med
OPENJSONi proceduren.