从存储过程结果集中在表上插入/更新数据

Posted

技术标签:

【中文标题】从存储过程结果集中在表上插入/更新数据【英文标题】:inserting/updating data on table from a stored procedure result set 【发布时间】:2011-06-01 07:46:14 【问题描述】:

我有一个名为 proc_item 的存储过程,它从不同的表中获取数据(使用连接)。我希望将存储过程的结果集插入(如果数据是新的)或更新(如果数据已经存在)到另一个名为 Item 的表上。任何人都可以给我一些关于我如何做到这一点的想法吗?我是 sql、存储过程和循环的新手。

谢谢

【问题讨论】:

如果您在记录中循环执行任何插入/更新或删除操作,那么您做错了。您应该将循环用作最后手段而不是首选技术。 【参考方案1】:

您应该创建一个临时表来保存存储过程的结果,然后将结果合并到您的表中。建议使用临时表而不是表变量,因为由于更好的统计信息,它会更好地加入现有表。

CREATE TABLE #TempResults
(
  Field1 DATATYPE1,
  Field2 DATATYPE2,
  ...,
  PRIMARY KEY CLUSTERED (KeyField1,...)
)

INSERT INTO #TempResults (Field1, Field2, ...)
   EXEC Schema.ProcName @Param1, ...

现在,有两种方法可以进行合并。第一个适用于所有版本的 SQL Server,第二个使用 SQL Server 2008 中引入的命令。

-- this should work on all SQL SERVER versions
UPDATE  rt
SET     rt.Field2 = tmp.Field2,
        ...
FROM    Schema.RealTable rt
INNER JOIN   #TempResults tmp
        ON   tmp.KeyField1 = rt.KeyField1
        ...

INSERT INTO Schema.RealTable (Field1, Field2, ...)
   SELECT  tmp.Field1, tmp.Field2, ...
   FROM    #TempResults tmp
   LEFT JOIN  Schema.RealTable rt
          ON  rt.KeyField1 = tmp.KeyField1
          ...
   WHERE   rt.KeyField1 IS NULL

或者:

-- the MERGE command was introduced in SQL SERVER 2008
MERGE Schema.RealTable AS target
USING (SELECT Field1, Field2,... FROM #TempResults) AS source (Field1, Field2,..)
ON (target.KeyField1 = source.KeyField1)
WHEN MATCHED THEN 
  UPDATE SET Field2 = source.Field2,
         ...
WHEN NOT MATCHED THEN   
  INSERT (Field1, Field2,...)
     SELECT  tmp.Field1, tmp.Field2, ...
     FROM    #TempResults tmp

有关 MERGE 命令的更多信息,请访问此处:http://msdn.microsoft.com/en-us/library/bb510625(v=SQL.100).aspx

现在,如果您要合并的结果集很大,并且您要合并的表非常大并且上面有很多活动,而这种类型的操作可能会导致一些阻塞,那么可以循环它来做集合一次1000行或类似的东西。类似这样的东西:

<insert CREATE TABLE / INSERT...EXEC block>

CREATE TABLE #CurrentBatch
(
  Field1 DATATYPE1,
  Field2 DATATYPE2,
  ...
)

DECLARE @BatchSize SMALLINT = ????

WHILE (1 = 1)
BEGIN

    -- grab a set to work on
    DELETE TOP (@BatchSize)
    OUTPUT deleted.Field1, deleted.Field2, ...
    INTO #CurrentBatch (Field1, Field2, ...)
    FROM #TempResults

    IF (@@ROWCOUNT = 0)
    BEGIN
        -- no more rows
        BREAK
    END

    <insert either UPDATE / INSERT...SELECT block or MERGE block from above
     AND change references to #TempResults to be #CurrentBatch>

    TRUNCATE TABLE #CurrentBatch

END

【讨论】:

这是一个比公认的更好的答案,它表明获得相同答案的技术很差。 srutzky,非常感谢您对这篇文章的回答。这非常有帮助。我想知道,您是否介意显示一次循环遍历 1k 行的必要代码。我计划在包含 >68k 行的表上使用此操作。谢谢。 @jpm0004,不客气,我在答案底部添加了示例。 @srutzky 谢谢!只是一个有趣的观察。我将原始过程应用于我的情况(没有循环),存储过程会比较两个表,每个表有超过 68k 行,并在大约 4 秒内进行更新。不太破旧。【参考方案2】:

看看@srutzky 的解决方案更适合这个问题


您可以做的第一件事就是将所有内容写入表格。 为此,您需要定义一个表,该表具有与存储过程返回的列相同的列:

DECLARE @myTempTableName TABLE(
                                dataRow1 DATATYPE,
                                dataRow2 DATATYPE,
                                ...
                               )

INSERT INTO @myTempTableName(dataRow1, dataRow2,...)
EXEC( *mystoredprocedure* )

现在您需要的所有数据都在表格中。下一步是检查您需要更新的内容和插入的内容。假设 datarow1 是检查它是否已经存在的变量(例如:相同的名称或相同的 id)AND 假设它是唯一的(否则您还需要一些独特的东西 - 迭代需要临时表)

DECLARE @rows INT,
        @dataRow1 DATATYPE,
        @dataRow2 DATATYPE, ...

-- COUNT Nr. of rows (how many rows are in the table)
SELECT 
    @rows = COUNT(1) 
FROM 
    @myTempTableName 

-- Loop while there are still some rows in the temporary table
WHILE (@rows > 0)

BEGIN

  -- select the first row and use dataRow1 as indicator which row it is. If dataRow1 is not unique the index should be provided by the stored procedure as an additional column
  SELECT TOP 1
    @dataRow1 = dataRow1,
    @dataRow2 = dataRow2, ..
  FROM
    @myTempTableName

  -- check if the value you'd like to insert already exists, if yes --> update, else --> insert
  IF EXISTS (SELECT * FROM *TableNameToInsertOrUpdateValues* WHERE dataRow1=@dataRow1)
      UPDATE 
            *TableNameToInsertOrUpdateValues*
      SET
            dataRow2=@dataRow2
      WHERE
            dataRow1=@dataRow1

  ELSE
      INSERT INTO 
            *TableNameToInsertOrUpdateValues* (dataRow1, dataRow2)
      VALUES 
            (@dataRow1, @dataRow2)


  --IMPORTANT: delete the line you just worked on from the temporary table
  DELETE FROM 
     @myTempTableName 
  WHERE 
     dataRow1= @dataRow1

  SELECT 
     @rows = COUNT(1) 
  FROM 
     @myTempTableName

END -- end of while-loop

可以在此查询的开头进行声明。我把它放在我使用它的地方,这样更容易阅读。

我的部分代码来自哪里,也有助于遍历表(@cmsjr 没有光标的解决方案):Cursor inside cursor

【讨论】:

没有理由用光标来做这件事。 很抱歉让您失望了。我还是 SQL 的新手 - 所以我的答案不是最好的方法,甚至不应该这样使用,我并不感到惊讶......我仍然在努力学习和改进...... 最让我担心的是它是公认的答案,而且许多其他搜索也可能导致不是最佳解决方案(它确实有效,但它不是解决数据库问题的最佳方法) . @skofgar,您可能想查看这篇文章,以帮助您了解解决这些类型问题的更好方法:wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them 我不会走那么远。您添加了一些内容以查看其他解决方案。【参考方案3】:

您需要先将数据插入到临时容器中,例如临时表或表变量。然后您可以照常使用该表:加入它、从中派生结果集等。

检查this question 以获取将 SP 的输出存储到表中的选项。

【讨论】:

以上是关于从存储过程结果集中在表上插入/更新数据的主要内容,如果未能解决你的问题,请参考以下文章

SQLCel匹配原数据信息,更新原数据所有信息并插入新数据的过程

在使用 Entity Framework 数据库优先在表上插入行之前,如何从 PL/SQL 执行触发器?

oracle数据库基本操作

mysql 存储过程 若主键冲突则更新,不冲突则插入数据

在更新或插入语句上没有事务 (?) 的单个表上的死锁

利用mysql存储过程循环插入新数据并更新