4

I have to call a MS SQLServer stored procedure with a table variable parameter.

/* Declare a variable that references the type. */
DECLARE @TableVariable AS [AList];

/* Add data to the table variable. */
INSERT INTO @TableVariable (val) VALUES ('value-1');
INSERT INTO @TableVariable (val) VALUES ('value-2');


EXEC  [dbo].[sp_MyProc]
            @param = @TableVariable

Works well in the SQL Sv Management studio. I tried the following in python using PyOdbc:

cursor.execute("declare @TableVariable AS [AList]")
for a in mylist:
    cursor.execute("INSERT INTO @TableVariable (val) VALUES (?)", a)
cursor.execute("{call dbo.sp_MyProc(@TableVariable)}")

With the following error: error 42000 : the table variable must be declared. THe variable does not survive the different execute steps. I also tried:

sql = "DECLARE @TableVariable AS [AList]; "
for a in mylist:
    sql = sql + "INSERT INTO @TableVariable (val) VALUES ('{}'); ".format(a)
sql = sql + "EXEC  [dbo].[sp_MyProc]   @param = @TableVariable"
cursor.execute(sql)

With the following error: No results. Previous SQL was not a query. No more chance with

sql = sql + "{call dbo.sp_MyProc(@TableVariable)}"

does somebody knows how to handle this using Pyodbc?

4

6 回答 6

5

现在问题的根源是 SQL Server变量具有定义它的批处理的范围。每次调用 cursor.execute 都是一个单独的批处理,即使它们在同一个事务中。

有几种方法可以解决这个问题。最直接的方法是重写您的 Python 代码,以便将所有内容作为一个批次发送。(我在我的测试服务器上对此进行了测试,只要您添加 set nocount on 或使用nextset 跳过中间结果,它就应该可以工作。)

一种更间接的方法是重写过程以查找临时表而不是表变量,然后只创建和填充临时表而不是表变量。未在存储过程中创建的临时表具有创建它的会话的范围。

于 2012-12-20T17:36:40.840 回答
4

我相信这个错误与sql忘记表变量无关。我最近遇到过这种情况,问题是如果 SP 还返回受影响事物的计数,pyodbc 不知道如何从存储过程中获取结果集。

在我的情况下,解决这个问题的方法是简单地将“SET NOCOUNT ON”放在 SP 的开头。

我希望这有帮助。

于 2012-12-20T17:07:38.927 回答
1

我不确定这是否有效并且我无法测试它,因为我没有 MS SQL Server,但是您是否尝试过在单个语句中执行所有内容:

cursor.execute("""
DECLARE @TableVariable AS [AList];

INSERT INTO @TableVariable (val) VALUES ('value-1');
INSERT INTO @TableVariable (val) VALUES ('value-2');

EXEC [dbo].[sp_MyProc] @param = @TableVariable;
""");
于 2012-11-14T11:46:54.197 回答
1

我有同样的问题,但这里的答案都没有解决它。我无法让“SET NOCOUNT ON”工作,也无法使用表变量进行单个批处理操作。起作用的是分两批使用临时表,但整天都在寻找正确的语法。下面的代码在第一批中创建并填充一个临时表,然后在第二批中,它使用数据库名称执行存储过程,并在存储过程名称前加上两个点。此语法对于避免错误“找不到存储过程'x'。(2812) (SQLExecDirectW))”很重要。

def create_incidents(db_config, create_table, columns, tuples_list, upg_date):
    """Executes trackerdb-dev mssql stored proc.
    Args:
        config (dict): config .ini file with mssqldb conn.
        create_table (string): temporary table definition to be inserted into 'CREATE TABLE #TempTable ()'
        columns (tuple): columns of the table table into which values will be inserted.
        tuples_list (list): list of tuples where each describes a row of data to insert into the table.
        upg_date (string): date on which the items in the list will be upgraded.
    Returns:
        None
    """

    sql_create = """IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
            DROP TABLE #TempTable;
        CREATE TABLE #TempTable ({});
        INSERT INTO #TempTable ({}) VALUES {};
        """
    columns = '"{}"'.format('", "'.join(item for item in columns))
    # this "params" variable is an egregious offense against security professionals everywhere. Replace it with parameterized queries asap.
    params = ', '.join([str(tupl) for tupl in tuples_list])
    sql_create = sql_create.format(
        create_table
        , columns
        , params)
    msconn.autocommit = True
    cur = msconn.cursor()
    try:
        cur.execute(sql_create)
        cur.execute("DatabaseName..TempTable_StoredProcedure ?", upg_date)
    except pyodbc.DatabaseError as err:
        print(err)
    else:
        cur.close()
    return

create_table = """
    int_column int
    , name varchar(255)
    , datacenter varchar(25)
    """

create_incidents(
    db_config    = db_config
, create_table = create_table
, columns      = ('int_column', 'name', 'datacenter')
, cloud_list   = tuples_list
, upg_date     = '2017-09-08')

存储过程使用IF OBJECT_ID('tempdb..#TempTable') IS NULL语法来验证临时表是否已创建。如果有,则程序从中选择数据并继续。如果尚未创建临时表,则 proc 中止。这会强制存储过程使用在存储过程本身之外但在同一会话中创建的#TempTable 的副本。pyodbc 会话一直持续到游标或连接关闭并且 pyodbc 创建的临时表具有整个会话的范围。

IF OBJECT_ID('tempdb..#TempTable') IS NULL
BEGIN
    -- #TempTable gets created here only because SQL Server Management Studio throws errors if it isn't.
    CREATE TABLE #TempTable (
        int_column int
        , name varchar(255)
        , datacenter varchar(25)
    );

    -- This error is thrown so that the stored procedure requires a temporary table created *outside* the stored proc
    THROW 50000, '#TempTable table not found in tempdb', 1;
END
ELSE
BEGIN
    -- the stored procedure has now validated that the temporary table being used is coming from outside the stored procedure
    SELECT * FROM  #TempTable;
END;

最后,请注意“tempdb”不是占位符,就像我第一次看到它时所想的那样。“tempdb”是一个实际的 MS SQL Server 数据库系统对象。

于 2017-09-09T04:06:28.407 回答
0

设置connection.autocommit = True和使用cursor.execute()一次而不是多次。您传递给的 SQL 字符串cursor.execute()必须包含所有 3 个步骤:

  1. 声明表变量
  2. 用数据填充表变量
  3. 执行使用该表变量作为输入的存储过程

3 个步骤之间不需要分号。

这是一个功能齐全的演示。我没有为参数传递而烦恼,因为它无关紧要,但它也可以正常工作,作为记录。

SQL 设置(提前执行)

CREATE TYPE dbo.type_MyTableType AS TABLE(
    a INT,
    b INT,
    c INT
)
GO

CREATE PROCEDURE dbo.CopyTable
    @MyTable type_MyTableType READONLY
AS
BEGIN
    SET NOCOUNT ON;
    SELECT * INTO MyResultTable FROM @MyTable
END

Python

import pyodbc

CONN_STRING = (
    'Driver={SQL Server Native Client 11.0};'
    'Server=...;Database=...;UID=...;PWD=...'
)

class DatabaseConnection(object):
    def __init__(self, connection_string):
        self.conn = pyodbc.connect(connection_string)
        self.conn.autocommit = True
        self.cursor = self.conn.cursor()

    def __enter__(self):
        return self.cursor

    def __exit__(self, *args):
        self.cursor.close()
        self.conn.close()

sql = (
    'DECLARE @MyTable type_MyTableType'
    '\nINSERT INTO @MyTable VALUES'
    '\n(11, 12, 13),'
    '\n(21, 22, 23)'
    '\nEXEC CopyTable @MyTable'
)

with DatabaseConnection(CONN_STRING) as cursor:
    cursor.execute(sql)

如果要将 SQL 分布在对 的多个调用中cursor.execute(),则需要改用临时表。请注意,在这种情况下,您仍然需要connection.autocommit = True.

于 2018-07-24T23:42:50.020 回答
0

正如Timothy指出的那样,问题在于使用 nextset()。

我发现,当您执行()多语句查询时,pyodbc 检查(是否存在任何语法错误)并仅执行批处理中的第一个语句而不是整个批处理,除非您明确指定 nextset()。

说你的查询是:

cursor.execute('select 1 '
               'select 1/0') 
print(cursor.fetchall())

你的结果是:

[(1, )]

但是一旦你通过命令指示它在批处理中进一步移动,这是语法错误的部分:

cursor.nextset()

你有它:

pyodbc.DataError: ('22012', '[22012] [Microsoft][ODBC SQL Server Driver][SQL Server]Divide by zero error encountered. (8134) (SQLMoreResults)')

因此解决了我在多语句查询中使用变量表时遇到的问题。

于 2020-09-04T15:02:45.503 回答