【问题标题】:Python call sql-server stored procedure with table valued parameterPython调用带有表值参数的sql-server存储过程
【发布时间】:2019-01-26 13:02:04
【问题描述】:

我有一个加载、转换和计算数据的 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?

【问题讨论】:

  • 什么版本的 SQL Server?此外,您始终可以像在第一个代码块中引用的那样运行完整的批处理,而不是从客户端绑定 TVP。
  • 错误是传递一个三元组作为参数序列的结果,但查询中只有一个占位符。快速浏览一下,您不应该有type_name="MatchColumnTable" 之类的吗?同样rows=(arg) 仅传递arg,而不是行序列,因为它是构成元组的逗号:rows=(arg,)(或者使用元组列表)。你的缩进也有点偏离。其余的不清楚。
  • @IljaEverilä 感谢您的提示并帮助我朝着正确的方向前进。我已经设法让脚本正常工作,我会在下面添加它作为答案

标签: python sql-server sqlalchemy table-valued-parameters python-db-api


【解决方案1】:

基于我的问题的 cmets,我设法让存储过程使用表值参数运行(并从 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 列。

除了 tvp 所需的参数都包含在 exec 字符串中。执行字符串中的最后一个参数是 tvp 参数的名称(mergeoncolumn),其中填充了 tvp。

您可以选择添加结果状态或行数,如 pytds 文档中所述: https://python-tds.readthedocs.io/en/latest/index.html

注意!:在存储过程中,您必须确保 添加了 SET NOCOUNT ON 否则您将不会将任何结果返回给 Python

【讨论】:

    【解决方案2】:

    pytds

    使用纯 Python TDS(表格数据流)协议实现的 MSSQL 的 Python DBAPI 驱动程序

    我通过stored procedurepytds 用于merge / upsert,以SQL Server 为目标。

    示例

    下面是一个基本函数的例子,一行数据用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)
    

    【讨论】:

      【解决方案3】:

      mssql+pyodbc://

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-10
        • 2018-11-27
        • 1970-01-01
        • 1970-01-01
        • 2017-11-02
        • 1970-01-01
        相关资源
        最近更新 更多