Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Como chamar o procedimento armazenado com SQLAlchemy que requer um parâmetro Table do tipo definido pelo usuário


Existe um driver que realmente suporta TVPs:Pytds . Não é oficialmente suportado, mas há uma implementação de dialeto de terceiros para isso:sqlalchemy-pytds . Usando-os, você pode chamar seu procedimento armazenado assim:
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',)]

Dessa forma, você pode passar grandes quantidades de dados 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',)

Se, por outro lado, você estiver usando um driver que não oferece suporte a TVPs, poderá declarar uma variável de tabela , insira os valores e passar isso como argumento ao seu procedimento:
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',)]

Observe que você não pode usar nenhum método executemany ou acabará chamando o procedimento para cada valor de tabela separadamente. É por isso que os placeholders são construídos manualmente e os valores da tabela são passados ​​como argumentos individuais. Deve-se tomar cuidado para não formatar nenhum argumento diretamente na consulta, mas sim a quantidade correta de espaços reservados para o DB-API. Os valores de linha são limitados a um máximo de 1000 .

É claro que seria bom, se o driver DB-API subjacente fornecesse suporte adequado para parâmetros com valor de tabela, mas pelo menos não consegui encontrar um caminho para o pymssql, que usa o FreeTDS. Uma referência a TVPs na lista de e-mails deixa claro que eles não são suportados. A situação não é muito melhor para PyODBC .

Isenção de responsabilidade:eu realmente não usei o MS SQL Server antes.