如何使用需要用户定义类型Table参数的SQLAlchemy调用存储过程

时间:2018-05-02 18:14:41

标签: python sql-server sqlalchemy flask-sqlalchemy

我在MSSQL服务器上有一个存储过程," prc_add_names",它接受一个表值参数。参数本身是自定义类型" StringTable"定义如下:

CREATE TYPE [dbo].[StringTable] AS TABLE([strValue] [nvarchar](max) NULL)

我不知道如何使用SQLAlchemy执行此过程。我习惯使用session.execute调用带有参数的过程,如下所示:

result = session.execute('prc_do_something :pArg', {pArg:'foo'})

但是,如果我只是传递一个字符串列表作为参数,那么这不起作用:

result = session.execute('prc_add_names :pArg', {pArg: ['Name One', 'Name Two']})

导致:

sqlalchemy.exc.ProgrammingError: (pymssql.ProgrammingError) (102, "Incorrect syntax near 'Name One'.DB-Lib error message 20018, severity 15:
General SQL Server error: Check messages from the SQL Server
") [SQL: 'prc_add_names %(pArg)s'] [parameters: {'pArg': ['Name One', 'Name Two']}] (Background on this error at: http://sqlalche.me/e/f405)

显然,SQLAlchemy不理解我的字符串列表是为了尝试创建我的StringTable类型的参数,但经过几个小时的谷歌搜索并阅读文档后,我还没弄明白我应该如何处理此

仅供参考,我无法控制此数据库,因此修改存储过程或其他任何内容都无法选择。

编辑:我没和SQLAlchemy结婚。如果有另一个库可以处理这个问题,我很乐意使用它。

2 个答案:

答案 0 :(得分:3)

有一个真正支持TVP的驱动程序:Pytds。它没有得到官方支持,但有第三方方言实现:sqlalchemy-pytds。使用它们,你可以这样调用你的存储过程:

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',)]

通过这种方式,您可以传递大量数据作为参数:

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',)

另一方面,如果您使用的驱动程序不支持TVP,则可以declare a table variable,将值和pass that as the argument插入到您的过程中:

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',)]

请注意,您不能使用任何executemany方法,或者您最终会分别为每个表值调用过程。这就是为什么手动构造占位符并将表值作为单独的参数传递的原因。必须注意不要将任何参数直接格式化到查询中,而是使用DB-API的正确占位符数量。行值仅限于maximum of 1000

如果底层的DB-API驱动程序为表值参数提供了适当的支持,那当然会很好,但至少我找不到使用FreeTDS的pymssql的方法。 reference to TVPs on the mailing list表明他们不受支持。情况为not much better for PyODBC

免责声明:之前我没有真正使用过MS SQL Server。

答案 1 :(得分:0)

我认为您可以使用callproc()方法并将输入参数作为列表传递。http://docs.sqlalchemy.org/en/latest/core/connections.html#calling-stored-procedures