sql >> Base de Datos >  >> RDS >> Sqlserver

Cómo llamar a un procedimiento almacenado con SQLAlchemy que requiere un parámetro de tabla de tipo definido por el usuario

Hay un controlador que realmente admite TVP:Pytds . No es compatible oficialmente, pero hay una implementación de dialecto de terceros para ello:sqlalchemy-pytds . Al usarlos, podría llamar a su procedimiento almacenado de la siguiente manera:

In [1]: engine.execute(DDL("CREATE TYPE [dbo].[StringTable] AS TABLE([strValue] [nvarchar](max) NULL)"))
Out[1]: <sqlalchemy.engine.result.ResultProxy at 0x7f235809ae48>

In [2]: engine.execute(DDL("CREATE PROC test_proc (@pArg [StringTable] READONLY) AS BEGIN SELECT * FROM @pArg END"))
Out[2]: <sqlalchemy.engine.result.ResultProxy at 0x7f2358027b70>

In [3]: arg = ['Name One', 'Name Two']

In [4]: import pytds

In [5]: tvp = pytds.TableValuedParam(type_name='StringTable',
   ...:                              rows=((x,) for x in arg))

In [6]: engine.execute('EXEC test_proc %s', (tvp,))
Out[6]: <sqlalchemy.engine.result.ResultProxy at 0x7f294e699e10>

In [7]: _.fetchall()
Out[7]: [('Name One',), ('Name Two',)]

De esta forma, puede pasar cantidades potencialmente grandes de datos como parámetros:

In [21]: tvp = pytds.TableValuedParam(type_name='StringTable',
    ...:                              rows=((str(x),) for x in range(100000)))

In [22]: engine.execute('EXEC test_proc %s', (tvp,))
Out[22]: <sqlalchemy.engine.result.ResultProxy at 0x7f294c6e9f98>

In [23]: _.fetchall()[-1]
Out[23]: ('99999',)

Si, por otro lado, está utilizando un controlador que no admite TVP, podría declarar una variable de tabla , inserte los valores y pasar eso como argumento a su procedimiento:

In [12]: engine.execute(
    ...:     """
    ...:     DECLARE @pArg AS [StringTable];
    ...:     INSERT INTO @pArg VALUES {placeholders};
    ...:     EXEC test_proc @pArg;
    ...:     """.format(placeholders=",".join(["(%s)"] * len(arg))),
    ...:     tuple(arg))
    ...:     
Out[12]: <sqlalchemy.engine.result.ResultProxy at 0x7f23580f2908>

In [15]: _.fetchall()
Out[15]: [('Name One',), ('Name Two',)]

Tenga en cuenta que no puede usar ningún método de ejecución múltiple o terminará llamando al procedimiento para cada valor de la tabla por separado. Es por eso que los marcadores de posición se construyen manualmente y los valores de la tabla se pasan como argumentos individuales. Se debe tener cuidado de no formatear ningún argumento directamente en la consulta, sino la cantidad correcta de marcadores de posición para DB-API. Los valores de fila están limitados a un máximo de 1000 .

Por supuesto, sería bueno si el controlador DB-API subyacente proporcionara el soporte adecuado para los parámetros con valores de tabla, pero al menos no pude encontrar una forma para pymssql, que usa FreeTDS. Una referencia a TVP en la lista de correo deja en claro que no son compatibles. La situación no es mucho mejor para PyODBC .

Descargo de responsabilidad:Realmente no he usado MS SQL Server antes.