我有一个python脚本,可以加载,转换和计算数据。在sql-server中,有一个存储过程,需要一个表值参数,2个必需参数和2个可选参数。在sql server中,我可以称呼此SP:
USE [InstName]
GO
DECLARE @return_value int
DECLARE @MergeOnColumn core.MatchColumnTable
INSERT INTO @MergeOnColumn
SELECT 'foo.ExternalInput','bar.ExternalInput'
EXEC @return_value = [core].[_TableData]
@Target = N'[dbname].[tablename1]',
@Source = N'[dbname].[table2]',
@MergeOnColumn = @MergeOnColumn,
@Opt1Param = False,
@Opt2Param = False
SELECT 'Return Value' = @return_value
GO
经过全面搜索,我发现了以下帖子:
How to call stored procedure with SQLAlchemy that requires a user-defined-type Table parameter
建议使用PYTDS和sql-alchemy的方言“ sql alchemy pytds”来调用具有表值参数的SP。 通过这篇文章和文档,我创建了以下Python脚本:
import pandas as pd
import pytds
from pytds import login
import sqlalchemy as sa
from sqlalchemy import create_engine
import sqlalchemy_pytds
def connect():
return pytds.connect(dsn='ServerName',database='DBName', auth=login.SspiAuth())
engine = sa.create_engine('mssql+pytds://[ServerName]', creator=connect)
conn = engine.raw_connection()
with conn.cursor() as cur:
arg = ("foo.ExternalInput","bar.ExternalInput")
tvp = pytds.TableValuedParam(type_name="MergeOnColumn", rows=(arg))
cur.execute('EXEC test_proc %s', ("[dbname].[table2]", "[dbname].[table1]", tvp,))
cur.fetchall()
运行此代码时,出现以下错误消息:
TypeError: not all arguments converted during string formatting
有人知道如何正确传递多个参数,或者有建议我可以直接处理此调用SP吗?
答案 0 :(得分:2)
pyodbc 在 2018-12-13 发布的 4.0.25 版本中添加了对表值参数 (TVP) 的支持。只需将 TVP 值作为元组列表提供:
proc_name = "so51930062"
type_name = proc_name + "Type"
# set up test environment
with engine.begin() as conn:
conn.exec_driver_sql(f"""\
DROP PROCEDURE IF EXISTS {proc_name}
""")
conn.exec_driver_sql(f"""\
DROP TYPE IF EXISTS {type_name}
""")
conn.exec_driver_sql(f"""\
CREATE TYPE {type_name} AS TABLE (
id int,
txt nvarchar(50)
)
""")
conn.exec_driver_sql(f"""\
CREATE PROCEDURE {proc_name}
@prefix nvarchar(10),
@tvp {type_name} READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT id, @prefix + txt AS new_txt FROM @tvp;
END
""")
#run test
with engine.begin() as conn:
data = {"prefix": "new_", "tvp": [(1, "foo"), (2, "bar")]}
sql = f"{{CALL {proc_name} (:prefix, :tvp)}}"
print(conn.execute(sa.text(sql), data).fetchall())
# [(1, 'new_foo'), (2, 'new_bar')]
答案 1 :(得分:1)
基于对我的问题的评论,我设法使存储过程以表值参数运行(并从SP获取返回值) 最终脚本如下:
import pandas as pd
import pytds
from pytds import login
import sqlalchemy as sa
from sqlalchemy import create_engine
import sqlalchemy_pytds
def connect():
return pytds.connect(dsn='ServerName',database='DBName',autocommit=True, auth=login.SspiAuth())
engine = sa.create_engine('mssql+pytds://[ServerName]', creator=connect)
conn = engine.raw_connection()
with conn.cursor() as cur:
arg = [["foo.ExternalInput","bar.ExternalInput"]]
tvp = pytds.TableValuedParam(type_name="core.MatchColumnTable", rows=arg)
cur.execute("EXEC test_proc @Target = N'[dbname].[tablename1]', @Source = N'[dbname].[table2]', @CleanTarget = 0, @UseColumnsFromTarget = 0, @MergeOnColumn = %s", (tvp,))
result = cur.fetchall()
print(result)
将自动提交添加到连接中(以在游标中提交事务),表值参数(marchcolumntable)需要2列,因此将arg修改为适合2列。
exec字符串中包含除tvp之外所需的参数。执行字符串中的最后一个参数是用tvp填充的tvp参数的名称(mergeoncolumn)。
(可选)您可以按照pytds文档中的描述添加结果状态或行数: https://python-tds.readthedocs.io/en/latest/index.html
注意!:在存储过程中,您必须确保 添加了SET NOCOUNT ON,否则您将无法将任何结果返回到Python
答案 2 :(得分:1)
用于MSSQL的Python DBAPI驱动程序,使用纯Python TDS(表格数据流)协议实现
我通过针对SQL Server的pytds将merge / upsert用于stored procedure。
这是基本功能的一个示例,行数据由Tuple表示:
def get_connection(instance: str, database: str, user: str, password: str):
return pytds.connect(
dsn=instance, database=database, user=user, password=password, autocommit=True
)
def execute_with_tvp(connection: pytds.Connection, procedure_name: str, rows: list):
with connection.cursor() as cursor:
tvp = pytds.TableValuedParam(type_name=my_type, rows=rows)
cursor.callproc(procedure_name, tvp)
对于我的最终解决方案,我放弃了python并实施了ac#程序,因为python的性能对于不错的ETL来说很低,而且pytds接缝不支持连接到使用非标准端口的命名实例。 Checkout the issue I created for updates.