【问题标题】:pyodbc, call stored procedure with table variablepyodbc,使用表变量调用存储过程
【发布时间】:2012-11-14 11:24:18
【问题描述】:

我必须使用表变量参数调用 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 管理工作室中运行良好。我使用 PyOdbc 在 python 中尝试了以下操作:

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 处理这个问题吗?

【问题讨论】:

    标签: python pyodbc


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

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

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

      我希望这会有所帮助。

      【讨论】:

      • 这绝对是其中的一部分,但您也可以使用光标上的 nextset 跳过计数。此外,您需要注意批次。我添加了一个解释它的答案。
      【解决方案3】:

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

      【讨论】:

      • 好吧,确实是这样,错误信息相同 = 未处理的编程错误:没有结果。以前的 SQL 不是查询
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        设置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
        

        蟒蛇

        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

        【讨论】:

          【解决方案6】:

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

          我发现,当您 execute() 多语句查询时,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)')
          

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-09-06
            • 1970-01-01
            • 1970-01-01
            • 2014-08-07
            • 2013-04-05
            • 2015-11-14
            • 1970-01-01
            相关资源
            最近更新 更多