我在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结婚。如果有另一个库可以处理这个问题,我很乐意使用它。
答案 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