Gespeicherte Prozeduren mit mssql-python aufrufen

Der mssql-python-Treiber implementiert die callproc() Methode aus der DB-API 2.0-Spezifikation nicht. Der Aufruf von callproc() löst NotSupportedError aus. Verwenden Sie stattdessen die ODBC-Escape-Sequenz {CALL ...} mit Standardmethoden zur Abfrageausführung.

Grundlegende Ausführung gespeicherter Prozeduren

Ohne Parameter

Führen Sie eine gespeicherte Prozedur mit der {CALL} Escape-Sequenz aus. Dieses Beispiel ruft die systemgespeicherte sp_databases Prozedur auf, die keine Parameter annimmt und pro Datenbank eine Zeile zurückgibt:

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)

Mit Eingabeparametern

Passparameter mit positionsbezogenen oder benannten Platzhaltern:

# Single parameter
cursor.execute(
    "{CALL dbo.uspGetManagerEmployees(?)}", (16,)
)

for row in cursor:
    print(row.FirstName, row.LastName)

Ausgabeparameter

Ausgabeparameter deklarieren und abrufen

SQL Server gespeicherte Prozeduren können Werte über Ausgabeparameter zurückgeben. Verwenden Sie Transact-SQL (T-SQL)-Variablen, um Ausgabewerte zu erfassen, und rufen Sie sie dann mit einer Anweisung SELECT ab:

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

Mehrere Ausgangsparameter

Erfassen Sie mehrere Ausgabewerte, indem Sie separate Variablen deklarieren. Das gleiche Muster funktioniert mit jedem gespeicherten Verfahren, das Parameter hat OUTPUT :

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

Rückgabewerte

Rückgabewert der gespeicherten Prozedur erfassen

Führe ein gespeichertes Verfahren aus, das Ergebnisse zurückgibt:

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

Rückgabewert mit Ausgabeparametern

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

Ergebnismengen

Einzelne Ergebnismenge

Führe eine gespeicherte Prozedur aus, die eine einzelne Ergebnismenge zurückgibt:

cursor.execute(
    "{CALL dbo.uspGetBillOfMaterials(?, ?)}",
    (800, "2026-01-01")
)

customers = cursor.fetchall()
for row in customers:
    print(f"{row.ProductAssemblyID}: {row.ComponentDesc}")

Mehrere Ergebnismengen

Einige gespeicherte Prozeduren geben mehrere Resultsets zurück. Verwenden Sie nextset(), um zwischen ihnen zu navigieren:

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

Suchen Sie nach weiteren Ergebnismengen

Durchlaufen Sie alle von einer gespeicherten Prozedur zurückgegebenen Ergebnismengen mit 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

Transaktionen mit gespeicherten Verfahren

Explizite Transaktionskontrolle

Mehrere gespeicherte Prozeduraufrufe in einer Transaktion einwickeln, um die Atomität sicherzustellen:

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

Lassen Sie die gespeicherte Prozedur die Transaktion verwalten

Wenn das gespeicherte Verfahren seine eigenen Transaktionen abwickelt:

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

Fehlerbehandlung

Fehler in gespeicherten Prozeduren abfangen

Behandle Ausnahmen, die durch gespeicherte Prozeduren oder Transact-SQL-Anweisungen ausgelöst werden:

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

Erfassen Sie PRINT-Anweisungen und Informationsnachrichten

SQL Server-Anweisungen PRINT und RAISERROR mit einem Schweregrad unter 11 werden nach der Ausführung in cursor.messages erfasst. Jeder Eintrag ist ein (message_type, message_text)-Tupel. Wenn eine PRINT vor einer Ergebnismenge ausgeführt wird, belegt sie eine eigene, zeilenlose Ergebnismenge. Lesen Sie daher zuerst cursor.messages und rufen Sie dann nextset() auf, um zu den Zeilen zu gelangen:

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

Wenn eine gespeicherte Prozedur MeldungenPRINT über mehrere Ergebnismengen ausgibt, lesen Sie cursor.messages nach execute() und erneut nach jedem Aufruf von nextset(), sodass Meldungen aus jeder Ergebnismenge erfasst werden:

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 die vollständige cursor.messages API siehe Cursor-Management.

Bewährte Methoden

Verwendung benannter Parameter

Benannte Parameter sind klarer und bewahren die Ordnungsunabhängigkeit:

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

Handhabung nullabler Ausgabeparameter

Überprüfen Sie NULL-Werte beim Abruf von Ausgabeparametern aus gespeicherten Prozeduren:

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

Verwenden von SET NOCOUNT ON in gespeicherten Prozeduren

Für eine bessere Leistung und eine sauberere Ergebnisbehandlung, stellen Sie sicher, dass Ihre gespeicherten Verfahren folgendes umfassen:

CREATE PROCEDURE dbo.MyProcedure
AS
BEGIN
    SET NOCOUNT ON;  -- Prevents "n rows affected" messages
    -- procedure logic
END

Beispiel: Vollständiger Arbeitsablauf

Hier ist ein praktisches Beispiel, das eine gespeicherte Prozedur aufruft, Ausgabewerte abruft und Fehler behandelt:

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)

Generierte Schlüssel mithilfe von OUTPUT INSERTED abrufen

Um einen Identitätswert aus einem INSERT abzurufen (mit oder ohne gespeicherte Prozedur), verwenden Sie OUTPUT INSERTED anstelle von SCOPE_IDENTITY(). Dieser Ansatz gibt den Wert im selben Ergebnissatz zurück:

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

Dieses Muster funktioniert für jede Tabelle mit einer Identitätsspalte und erfordert keine gespeicherte Prozedur.

Nicht unterstützte Funktionen

callproc()

Der mssql-python Treiber löst NotSupportedError aus, wenn Sie cursor.callproc() aufrufen. Verwenden Sie stattdessen cursor.execute("{CALL ...}") oder cursor.execute("EXECUTE ..."), wie in diesem Artikel durchgängig gezeigt.

Tabellenwertparameter (TVPs)

Tabellenwerte Parameter werden in der aktuellen Version (1.11.0) von mssql-pythonnicht unterstützt. Wenn Sie eine Reihe von Zeilen an eine gespeicherte Prozedur übergeben müssen, verwenden Sie Alternativen:

  • Füge zuerst in eine Temp-Tabelle ein und lass dann die gespeicherte Prozedur daraus lesen.
  • Verwenden Sie bulkcopy(), um Daten in eine Stagingtabelle zu laden.
  • Übergeben Sie einen JSON-String und parsen Sie ihn innerhalb der Prozedur mit OPENJSON.