pyodbc,用表变量调用存储过程

时间:2012-11-14 11:24:18

标签: python pyodbc

我必须使用表变量参数调用MS SQLServer存储过程。

/* 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

在SQL Sv Management工作室中运行良好。我在python中使用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)}")

出现以下错误:错误42000:必须声明表变量。变量不能在不同的执行步骤中存活。 我也尝试过:

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)

出现以下错误:无结果。以前的SQL不是查询。

没有机会了
sql = sql + "{call dbo.sp_MyProc(@TableVariable)}"

有人知道如何使用Pyodbc处理这个问题吗?

6 个答案:

答案 0 :(得分:4)

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

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

我希望这会有所帮助。

答案 1 :(得分:1)

现在问题的根源是SQL Server variable具有定义的批次的范围。对cursor.execute的每次调用都是一个单独的批处理,即使他们在同一笔交易中。

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

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

答案 2 :(得分:0)

我不确定这是否有效且我无法测试它,因为我没有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;
""");

答案 3 :(得分:0)

我有同样的问题,但这里的答案都没有解决。我无法使“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数据库系统对象。

答案 4 :(得分:0)

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

  1. 声明表变量
  2. 用数据填充表变量
  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

答案 5 :(得分:0)

正如Timothy所指出的,要抓住的地方是使用nextset()。

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

说您的查询是:

ClientBuilder/WebTarget)

您的结果是:

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

,但是只要您通过命令指示它进一步移至语法上错误的部分,则该批处理:

[(1, )]

有它:

cursor.nextset()

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