mssql-python ile depolanmış prosedürleri çağırın

mssql-python sürücüsü, DB-API 2.0 spesifikasyonundan bu callproc() yöntemi uygulamaz. callproc() çağrılması NotSupportedError yükseltir. Bunun yerine, standart sorgu yürütme yöntemleriyle ODBC {CALL ...} kaçış dizisini kullanın.

Temel kayıtlı prosedür yürütme

Parametresiz

Escape dizisini kullanarak {CALL} bir saklanan prosedürü çalıştırın. Bu örnek, parametre almayan ve veritabanı başına bir satır döndüren sp_databases sistem saklı yordamını çağırır:

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)

Giriş parametreleriyle

Parametreleri konumsal veya adlandırılmış yer tutucular kullanarak geçirin:

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

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

Çıkış parametreleri

Çıkış parametrelerini bildir ve geri getir

SQL Server depolanmış prosedürleri, çıktı parametreleri aracılığıyla değerleri döndürebilir. Çıktı değerlerini yakalamak için Transact-SQL (T-SQL) değişkenleri kullanın, ardından bunları bir SELECT ifadeyle alın:

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

Çoklu çıkış parametreleri

Ayrı değişkenler ilan ederek birden fazla çıktı değerini yakalayın. Aynı desen, parametreleri OUTPUT olan herhangi bir depolanmış prosedürde de çalışır:

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

Dönüş değerleri

Saklanan prosedür dönüş değerini yakalayın

Sonuçları geri getiren bir kayıtlı prosedür çalıştırın:

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

Çıkış parametreleriyle dönüş değeri

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

Sonuç kümeleri

Tek sonuç kümesi

Tek bir sonuç kümesi döndüren bir saklanan prosedürü çalıştırın:

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

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

Birden fazla sonuç kümesi

İlerlediğiniz her sonuçta nextset(), rowcount öğesini yeniler. Bir veri değiştirme sonucu kendi etkilenen satır sayısını bildirir ve description is None vardır; bu sonuç için fetch metodlarını çağırmayın. Satır üreten sonuç, getirmeden önce rowcount == -1 ile başlar. SET NOCOUNT ON etkilenen satır sonuçlarının görüntülenmesini engeller. Bu sonuç türlerini içeren bir toplu işlem için, Bir toplu işlemde her sonucu izleme bölümüne bakın.

Bazı saklı yordamlar birden çok sonuç kümesi döndürür. Bunlar arasında gezinmek için kullanın nextset() :

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

Daha fazla sonuç seti için kontrol edin

Saklanan bir prosedür tarafından geri dönen tüm sonuç kümelerini şu şekilde nextset()yinele:

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

Depolanmış prosedürlerle işlemler

Açık işlem kontrolü

Atomikliği sağlamak için birden fazla depolanmış prosedür çağrısını bir işlem içine alın:

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

Saklanan prosedürün işlemi yönetmesine izin verin

Eğer saklanan prosedür kendi işlemlerini yönetiyorsa:

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

Hata yönetimi

Saklanan prosedür hatalarını yakalayın

Depolanmış prosedürler veya Transact-SQL ifadeleri tarafından ortaya çıkan istisnaları yönetin:

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

PRINT ifadeleri ve bilgilendirici mesajları yakalayın

SQL Server PRINT deyimleri ve önem derecesi 11'in altında olan RAISERROR yürütmeden sonra cursor.messages içinde yakalanır. Her giriş bir (message_type, message_text) tuple'dır. Bir PRINT, sonuç kümesinden önce çalıştığında kendine ait, satır içermeyen bir sonuç kümesi oluşturur; bu nedenle önce cursor.messages öğesini okuyun, ardından satırlara ulaşmak için nextset() çağrısını yapın:

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

Bir saklı yordam, birden çok sonuç kümesi genelinde PRINT mesajları ürettiğinde, her sonuç kümesindeki mesajların yakalanmasını sağlamak için cursor.messages sonrasında ve her execute() çağrısından sonra yeniden nextset() okuyun:

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

Tam cursor.messages API için bkz. İmleç yönetimi.

En iyi uygulamalar

Adlandırılmış parametreleri kullanın

Adlandırılmış parametreler daha açıktır ve sıra bağımsızlığı sağlar:

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

Null atanabilir çıkış parametrelerini işleme

Depolanmış prosedürlerden çıktı parametreleri alınırken NULL değerleri kontrol edin:

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

Saklı yordamlarda SET NOCOUNT ON kullanın

Daha iyi performans ve daha temiz sonuç yönetimi için, depolanmış prosedürlerinizin şunları içerdiğinden emin olun:

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

Örnek: Tam iş akışı

İşte bir saklanan prosedürü çağıran, çıktı değerlerini alan ve hataları yöneten pratik bir örnek:

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)

OUTPUT INSERTED kullanarak oluşturulan anahtarları alın

INSERT öğesinden bir kimlik değeri almak için (saklı yordamla veya saklı yordam olmadan), OUTPUT INSERTED yerine SCOPE_IDENTITY() kullanın. Bu yaklaşım, aynı sonuç kümesinde değeri döndürür:

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

Bu desen, kimlik sütunu olan herhangi bir tablo için çalışır ve bir işlem kaydedilemez.

Desteklenmeyen özellikler

callproc()

mssql-python çağırırsanız sürücü NotSupportedError oluştururcursor.callproc(). Bunun yerine, bu makale boyunca gösterildiği gibi cursor.execute("{CALL ...}") veya cursor.execute("EXECUTE ...") kullanın.

Tablo değerine sahip parametreler (TVP'ler)

mssql-python sürücü tablo değerli parametreleri desteklemiyor. Tam özellik destek matrisi için Destek yaşam döngüsü için bkz. Bir satır setini bir depolanmış prosedüre aktarmanız gerekiyorsa, şu alternatifleri kullanın:

  • Önce geçici bir tabloya ekle, sonra saklanan prosedürü oradan okun.
  • Verileri bir aşamalama tablosuna yüklemek için kullanılır bulkcopy() .
  • Bir JSON dizesi geçirin ve bunu prosedür içinde OPENJSON ile ayrıştırın.