Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Драйвер mssql-python не реализует метод callproc() из спецификации DB-API 2.0. Вызов callproc() вызывает NotSupportedError. Вместо этого используйте последовательность escape ODBC {CALL ...} со стандартными методами выполнения запросов.
Базовое выполнение сохранённой процедуры
Без параметров
Выполните сохранённую процедуру, используя {CALL} последовательность escape. В этом примере вызывается системная хранимая процедура sp_databases, которая не принимает параметров и возвращает по одной строке для каждой базы данных:
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)
С входными параметрами
Передавайте параметры с помощью позиционных или именованных заполнителей:
# Single parameter
cursor.execute(
"{CALL dbo.uspGetManagerEmployees(?)}", (16,)
)
for row in cursor:
print(row.FirstName, row.LastName)
Выходные параметры
Объявить и получить выходные параметры
Хранимые процедуры SQL Server могут возвращать значения через выходные параметры. Используйте переменные Transact-SQL (T-SQL) для сохранения выходных значений, затем извлеките их с помощью оператора SELECT:
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}")
Множественные параметры выхода
Фиксируйте несколько выходных значений, объявляя отдельные переменные. Тот же паттерн работает с любой хранящейся процедурой, имеющей 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}")
Возвращаемые значения
Получить возвращаемое значение хранимой процедуры
Выполните сохранённую процедуру, которая возвращает результаты:
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")
Возвращаемое значение с параметрами вывода
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}")
Наборы результатов
Набор однорезультатных результатов
Выполните сохранённую процедуру, которая возвращает один набор результатов:
cursor.execute(
"{CALL dbo.uspGetBillOfMaterials(?, ?)}",
(800, "2026-01-01")
)
customers = cursor.fetchall()
for row in customers:
print(f"{row.ProductAssemblyID}: {row.ComponentDesc}")
Множество результирующих наборов
Некоторые хранимые процедуры возвращают несколько результирующих наборов. Используйте 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}")
Проверьте наличие дополнительных наборов результатов
Пройтись по всем наборам результатов, возвращаемым сохранённой процедурой, используя 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
Транзакции с хранящимися процедурами
Явное управление транзакциями
Оберните несколько вызовов хранимых процедур в транзакцию для обеспечения атомарности:
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}")
Пусть хранимая процедура управляет транзакцией
Если сохранённая процедура обрабатывает собственные транзакции:
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")
Обработка ошибок
Ошибки обнаружения сохранённой процедуры
Обрабатывайте исключения, вызванные хранящимися процедурами или операторами Transact-SQL:
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 и информационных сообщений
Операторы SQL Server PRINT и RAISERROR с уровнем серьезности ниже 11 фиксируются в cursor.messages после выполнения. Каждая запись представляет собой кортеж (message_type, message_text). Когда PRINT предшествует набору результатов, он занимает отдельный набор результатов без строк, поэтому сначала считайте cursor.messages, а затем вызовите nextset(), чтобы перейти к строкам:
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}")
Когда хранимая процедура выдаёт сообщения PRINT в нескольких наборах результатов, считывайте cursor.messages после execute(), а затем снова после каждого вызова nextset(), чтобы получать сообщения из каждого набора результатов:
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}")
Полный cursor.messages API смотрите в разделе «Управление курсорами».
Лучшие практики
Используйте именованные параметры
Названные параметры более чёткие и сохраняют независимость порядка:
# 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 при извлечении выходных параметров из хранящихся процедур:
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")
Используйте SET NOCOUNT ON в хранимых процедурах
Для лучшей производительности и более чистой обработки результатов убедитесь, что ваши хранящиеся процедуры включают:
CREATE PROCEDURE dbo.MyProcedure
AS
BEGIN
SET NOCOUNT ON; -- Prevents "n rows affected" messages
-- procedure logic
END
Пример: Полный рабочий процесс
Вот практический пример, который вызывает сохранённую процедуру, получает выходные значения и обрабатывает ошибки:
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
Чтобы получить тождественное значение из INSERT (с хранящейся процедурой или без), используйте OUTPUT INSERTED вместо SCOPE_IDENTITY(). Этот подход возвращает значение в том же наборе результатов:
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}")
Этот паттерн работает для любой таблицы с столбцем идентичности и не требует сохранённой процедуры.
Неподдерживаемые функции
callproc()
Драйвер mssql-python вызывает NotSupportedError, если вы вызываете cursor.callproc(). Используйте cursor.execute("{CALL ...}") или cursor.execute("EXECUTE ...") вместо этого, как показано в этой статье.
Параметры, представленные в виде таблицы (TVP)
Табличные параметры не поддерживаются в текущей версии (1.12.0) mssql-python. Если нужно передать набор строк в сохранённую процедуру, используйте альтернативы:
- Сначала вставьте её в временную таблицу, затем пусть сохранённая процедура будет прочитана из неё.
- Используйте
bulkcopy(), чтобы загрузить данные в промежуточную таблицу. - Передайте строку JSON и разберите её с помощью
OPENJSONвнутри процедуры.