Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El controlador mssql-python no implementa el callproc() método de la especificación DB-API 2.0. Llamar a callproc() genera NotSupportedError. En su lugar, utiliza la secuencia de escape ODBC {CALL ...} con métodos estándar de ejecución de consultas.
Ejecución básica de procedimientos almacenados
Sin parámetros
Ejecuta un procedimiento almacenado mediante la secuencia de escape {CALL}. Este ejemplo llama al sp_databases procedimiento almacenado del sistema, que no toma parámetros y devuelve una fila por base de datos:
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)
Con parámetros de entrada
Pasar parámetros usando marcadores posicionales o con nombre:
# Single parameter
cursor.execute(
"{CALL dbo.uspGetManagerEmployees(?)}", (16,)
)
for row in cursor:
print(row.FirstName, row.LastName)
Parámetros de salida
Declarar y recuperar parámetros de salida
Los procedimientos almacenados de SQL Server pueden devolver valores a través de parámetros de salida. Utiliza variables Transact-SQL (T-SQL) para capturar los valores de salida y luego recupérelos mediante una instrucción 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}")
Múltiples parámetros de salida
Captura múltiples valores de salida declarando variables separadas. El mismo patrón funciona con cualquier procedimiento almacenado que tenga OUTPUT parámetros:
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}")
Valores devueltos
Captura el valor de retorno del procedimiento almacenado
Ejecuta un procedimiento almacenado que devuelva resultados:
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")
Valor de retorno con parámetros de salida
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}")
Conjuntos de resultados
Conjunto de resultados único
Ejecuta un procedimiento almacenado que devuelva un único conjunto de resultados:
cursor.execute(
"{CALL dbo.uspGetBillOfMaterials(?, ?)}",
(800, "2026-01-01")
)
customers = cursor.fetchall()
for row in customers:
print(f"{row.ProductAssemblyID}: {row.ComponentDesc}")
Varios conjuntos de resultados
Algunos procedimientos almacenados devuelven varios conjuntos de resultados. Use nextset() para navegar entre ellos:
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}")
Consulta si hay más conjuntos de resultados
Recorrer todos los conjuntos de resultados devueltos por un procedimiento almacenado mediante 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
Transacciones con procedimientos almacenados
Control explícito de transacciones
Encapsule varias llamadas a procedimientos almacenados en una transacción para garantizar la atomicidad:
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}")
Deja que el procedimiento almacenado gestione la transacción
Si el procedimiento almacenado gestiona sus propias transacciones:
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")
Gestión de errores
Detectar errores en procedimientos almacenados
Gestiona las excepciones generadas por procedimientos almacenados o sentencias 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}")
Capturar instrucciones PRINT y mensajes informativos
Las instrucciones de SQL Server PRINT y RAISERROR con gravedad inferior a 11 se capturan en cursor.messages tras su ejecución. Cada entrada es de tipo tupla (message_type, message_text). Cuando PRINT se ejecuta antes de un conjunto de resultados, ocupa un conjunto de resultados propio, sin filas, así que lee primero cursor.messages y luego llama a nextset() para acceder a las filas:
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}")
Cuando un procedimiento almacenado emite PRINT mensajes a través de múltiples conjuntos de resultados, lee cursor.messages después execute() y de nuevo después de cada nextset() llamada para que se capturen mensajes de cada conjunto de resultados:
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}")
Para la API completa cursor.messages , véase Gestión de cursores.
procedimientos recomendados
Uso de parámetros nombrados
Los parámetros nombrados son más claros y mantienen la independencia del orden:
# 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"))
Manejar parámetros de salida anulables
Comprueba los valores NULL al recuperar los parámetros de salida de procedimientos almacenados:
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")
Uso SET NOCOUNT de ON en procedimientos almacenados
Para un mejor rendimiento y un manejo de resultados más limpio, asegúrate de que tus procedimientos almacenados incluyan:
CREATE PROCEDURE dbo.MyProcedure
AS
BEGIN
SET NOCOUNT ON; -- Prevents "n rows affected" messages
-- procedure logic
END
Ejemplo: flujo de trabajo completo
Aquí tienes un ejemplo práctico que llama a un procedimiento almacenado, recupera valores de salida y gestiona errores:
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)
Consigue claves generadas usando OUTPUT INSERTED
Para recuperar un valor identidad de un INSERT (con o sin procedimiento almacenado), use OUTPUT INSERTED en lugar de SCOPE_IDENTITY(). Este enfoque devuelve el valor en el mismo conjunto de resultados:
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}")
Este patrón funciona para cualquier tabla con columna identidad y no requiere un procedimiento almacenado.
Características no admitidas
callproc()
El controlador mssql-python genera NotSupportedError si llama a cursor.callproc(). Úsate cursor.execute("{CALL ...}") o cursor.execute("EXECUTE ...") , en su lugar, como se muestra a lo largo de este artículo.
Parámetros con valores de tabla (TVPs)
El mssql-python controlador no soporta parámetros con valores de tabla. Para ver la matriz completa de soporte de funciones, véase ciclo de vida de soporte. Si necesitas pasar un conjunto de filas a un procedimiento almacenado, utiliza estas alternativas:
- Inserta primero en una tabla temporal y luego haz que el procedimiento almacenado sea leído de ella.
- Usa
bulkcopy()para cargar datos en una tabla de ensayo. - Pasa una cadena JSON y analízala con
OPENJSONdentro del procedimiento.