【问题标题】:Is it possible to pass in Table-Valued Parameters to PYODBC query?是否可以将表值参数传递给 PYODBC 查询?
【发布时间】:2021-03-01 18:06:21
【问题描述】:

我知道我可以将 TVP 传递给存储过程(因为这是我之前在这里问过的问题)。我现在想知道这是否是我可以在 Python 中做的事情。我不断收到此错误。就是说有一个无效的类型,那么参数需要知道我传入的是什么类型吗?

pyodbc.ProgrammingError: ('42000', '[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]列、参数或变量 #1:找不到只读数据类型。(2715) ( SQLExecDirectW); [42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]必须声明表变量“@P1”。(1087); [42000] [Microsoft][ODBC Driver 17 for SQL Server][ SQL Server]无法准备语句。(8180);[42000][Microsoft][ODBC Driver 17 for SQL Server][SQL Server]参数或变量“@P1”的数据类型无效。(2724) ')

from src.data.dbaccess import con

queryString2 = """ 

SET NOCOUNT ON

DECLARE @t As Table(
PersonName VARCHAR(50)
)

INSERT INTO @t
SELECT *
FROM ?

SELECT * FROM @t
"""

data = ([('Jim', ), ('Bob', )],)

with pyodbc.connect(con.conString) as testCon:
    crsr = testCon.execute(queryString2, data)
    for row in crsr:
        print(row)

** 编辑澄清。在我去年的问题中,我问我是否可以将 TVP 传递给 SQL Server 存储过程。这一次,程序本身就是我的 Python 代码。这是我现在要解决的问题。

【问题讨论】:

  • 不,这实际上是我去年提出的问题。这次的不同之处在于,与上次的 SQL Server 存储过程相比,我拥有 Python 中的全部代码。
  • 哎呀,这很有趣 :-) 但是看看你当前的代码:你应该只通过 TVP 作为名称为@t 的参数,然后你可以直接从中选择。我完全不明白你为什么想要那个insert。还是 pyodbc 强制名称为 @p1 然后就使用它?
  • 我相当肯定你所描述的是不可能的。匿名代码块的问题在于它没有像存储过程那样声明输入和输出参数的机制,并且 SQL Server 要求将 TVP 声明为 READONLY 参数。 SQL Server 确实提供了一种声明临时存储过程(类似于临时表)的方法,但我只是尝试了一个快速测试,并且临时存储过程显然不能接受用户定义的类型作为参数。
  • @Charlieface 我认为他指的是一种使用 PYODBC 的方法。

标签: python sql-server pyodbc


【解决方案1】:

正如对该问题的评论中所述,可以使用 System.Data.SqlClient 从 C# 将 TVP 传递给匿名代码块,如下所示:

var tvpData = new DataTable();
tvpData.Columns.Add(new DataColumn("id", Type.GetType("System.Int32")));
tvpData.Rows.Add(new object[] { 1 });
tvpData.Rows.Add(new object[] { 2 });

using (var con = new SqlConnection(@"Server=127.0.0.1,49242;Database=mydb;Trusted_Connection=True;"))
{
    con.Open();
    using (var cmd = new SqlCommand("SELECT * FROM @p1", con))
    {
        SqlParameter tvpParam = cmd.Parameters.AddWithValue("@p1", tvpData);
        tvpParam.SqlDbType = SqlDbType.Structured;
        tvpParam.TypeName = "dbo.dboListInt";  // an existing user-defined table type

        SqlDataReader rdr = cmd.ExecuteReader();
        Console.WriteLine("rows returned:");
        while (rdr.Read())
        {
            Console.WriteLine(rdr[0]);
        }
    }
}

控制台输出:

rows returned:
1
2

但是,这不适用于使用 System.Data.Odbc 的 C# 的 ODBC 连接,因为代码依赖于 SqlClient 特定的 SqlDbType.Structured 类型。

pyodbc 没有用于指定“结构化”(TVP)参数类型的类似机制。相反,它通过它接收的参数的“形状”来推断表值参数的存在(例如,由元素元组表示的行,其中一个元素本身就是元组列表)。

此外,pyodbc 和 ODBC 驱动程序之间的对话目前并没有将用户定义的类型完全考虑为除普通存储过程调用之外的任何参数。 (如果我们可以使用临时存储过程——CREATE PROCEDURE #myTempSP …——那就太好了——但测试表明临时存储过程不能与用户定义的表类型一起使用。)

TL;DR — TVP 可以从 C# 传递给匿名代码块,但(目前)不能通过 pyodbc 从 Python 传递。

【讨论】:

  • 感谢上帝。似乎我们对 PYODBC 的主要两个选项是使用存储过程或将数据读入 Python,对其进行处理,然后将其重新插入数据库(如果使用 fast_executemany 插入多行)。
  • 如果我们已经有“表格形式”的 Python 数据(“行”列表,其中每个“行”是“列”的元组),否则我们可以直接将其作为 TVP 传递始终将它们作为#temp 表推送到服务器。然后匿名代码块(使用相同的连接)可以将值拉入用户定义的表变量中,如图所示here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
  • 1970-01-01
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多