简体   繁体   English

Python调用带有表值参数的sql-server存储过程

[英]Python call sql-server stored procedure with table valued parameter

I have a python script that loads , transform and calculates data.我有一个加载、转换和计算数据的 python 脚本。 In sql-server there's a stored procedure that requires a table valued parameter, 2 required parameters and 2 optional parameters.在 sql-server 中有一个存储过程,它需要一个表值参数、2 个必需参数和 2 个可选参数。 In sql server I can call this SP:在 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

after a comprehensive search I found the following post:经过全面搜索,我找到了以下帖子:

How to call stored procedure with SQLAlchemy that requires a user-defined-type Table parameter 如何使用需要用户定义类型表参数的 SQLAlchemy 调用存储过程

it suggests to use PYTDS and the sql-alchemy 's dialect 'sql alchemy pytds' to call a SP with table valued parameters.它建议使用 PYTDS 和 sql-alchemy 的方言“sql alchemy pytds”来调用具有表值参数的 SP。 with this post and the documentation I created the following Python script:通过这篇文章和文档,我创建了以下 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()

When I run this code I get the following error message:当我运行此代码时,我收到以下错误消息:

TypeError: not all arguments converted during string formatting

Doe anyone know how to pass in the multiple arguments correctly or has a suggestion how I could handle this call SP directly?有谁知道如何正确传递多个参数或有我如何直接处理这个调用 SP 的建议?

On the basis of the comments to my question i've managed to get the stored procedure running with table valued parameters (and get the return values from the SP) The final script is as follows:根据对我的问题的评论,我设法使用表值参数运行存储过程(并从 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)

The autocommit is added in the connection (to commit the transaction in the cursor), the table valued parameter (marchcolumntable) expects 2 columns, so the arg is modified to fit 2 columns.在连接中添加了自动提交(以提交游标中的事务),表值参数(marchcolumntable)需要 2 列,因此修改了 arg 以适合 2 列。

The parameters that are required besides the tvp are included in the exec string.除了 tvp 之外所需的参数都包含在 exec 字符串中。 The last param in the execute string is the name of the tvp parameter(mergeoncolumn) that is filled with the tvp.执行字符串中的最后一个参数是用 tvp 填充的 tvp 参数(mergeoncolumn)的名称。

optionally you can add the result status or row count as descripted in the pytds documentation: https://python-tds.readthedocs.io/en/latest/index.html您可以选择添加 pytds 文档中描述的结果状态或行数: https ://python-tds.readthedocs.io/en/latest/index.html

Note!笔记! : in the stored procedure you have to make sure that the SET NOCOUNT ON is added otherwise you wont get any results back to Python :在存储过程中,您必须确保添加了 SET NOCOUNT ON,否则您不会将任何结果返回给 Python

pytds pytds

Python DBAPI driver for MSSQL using pure Python TDS (Tabular Data Stream) protocol implementation使用纯 Python TDS(表格数据流)协议实现的 MSSQL 的 Python DBAPI 驱动程序

I used pytds for merge / upsert via a stored procedure targeting a SQL Server.我通过针对 SQL Server 的存储过程使用pytds进行合并/更新插入

Example例子

Here are a example of the basic functions, a row data is represented by 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)

mssql+pyodbc:// mssql+pyodbc://

pyodbc added support for table-valued parameters (TVPs) in version 4.0.25, released 2018-12-13. pyodbc 在 2018-12-13 发布的 4.0.25 版本中添加了对表值参数 (TVP) 的支持。 Simply supply the TVP value as a list of tuples:只需将 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')]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM