【问题标题】:Complex TSQL Merge复杂的 TSQL 合并
【发布时间】:2011-11-21 23:32:45
【问题描述】:

我“继承了”一段出色的 TSQL 代码可以做到这一点:

  • 在游标上逐行循环。
  • 游标包含表A中需要合并(Upserted)的数据
  • 对于游标中的每个行循环,都会调用一个存储过程。过程:
    • 如果表 A 中存在相应的行,则更新该行
    • 如果这样的行不存在,则:
      • 在不同的表 B 中插入一行。
      • 获取新生成的 ID(比如它称为 IDB)
      • 在表 A 中插入单行。表 A 插入 需要一个 IDB(该字段不为空,它应该只有表 B 中的值,但没有 FK 约束)

显然这很糟糕(性能和优雅的原因)!!

问题 起初,这看起来像是 MERGE 使用的标准案例。我试着做:

MERGE [dbo].[TableA] AS Target
USING <cursor data set as a select statement> as Src on target.IDA = Src.IDA
WHEN MATCHED 
  //update
WHEN NOT MATCHED
//insert <------ Fails because obviously a new IDB is required

还尝试了各种方法,例如 nested select that sends IDB on the OUTPUT,但由于 IDB 是 PK 导致失败。

其他类型的合并也失败了,例如:

MERGE Table A with <cursor data set as a select statement>
...
MERGE Table A with Table B
WHEN NOT MATCHED
//insert on Table A
WHEN NOT MATCHED
// Update Table B

有人对此有想法吗?从本质上讲,我认为如果我们将问题概括为:

Can I insert and return the PK in one statement that can be nested in other statements

提前感谢您的任何回复

乔治

【问题讨论】:

  • 你可以合并到tableB并使用technique here我认为。
  • 谢谢,看到了。它实际上是一个好主意。本质上,这家伙将新生成的 Id 存储在临时变量中。与我正在寻找的不同(在 1 Merge 中完成整个过程),但它仍然是一种有趣的方法。谢谢马丁。
  • +1 用于使用“upserted”这个词...:)
  • 什么是 IDB?是否可以将该 IDB 更改为自动增量或某种默认值?此外,您能否将生成新 ID 的行为封装到一个函数中,然后在您的合并语句中使用它?

标签: sql-server tsql sql-server-2008 merge upsert


【解决方案1】:

如果您在 TableB 上有一个自动生成的 PK,您可以使用与此类似的代码。否则,只需将 INSERT 更改为 TableA 即可先从 TableB 中获取 PK。

DECLARE @OldData CHAR(10)
SET @OldData = 'Old'
DECLARE @NewData CHAR(10)
SET @NewData = 'New'

CREATE TABLE #TableA 
(
    IDA INT IDENTITY(1,1) PRIMARY KEY,
    IDB INT NOT NULL,
    DataA CHAR(10)
)

CREATE TABLE #TableB 
(
    IDB INT IDENTITY(1,1) PRIMARY KEY,
    DataB CHAR(10)
)

DECLARE @IDsToUpsert TABLE
(
    ID INT
)

-- Add test values for existing rows 
INSERT INTO #TableB
OUTPUT INSERTED.IDB, @OldData
INTO #TableA
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData 

-- Add test values for the rows to upsert
INSERT INTO @IDsToUpsert
SELECT 1 UNION -- exists
SELECT 3 UNION -- exists
SELECT 5 UNION -- does not exist
SELECT 7 UNION -- does not exist
SELECT 9       -- does not exist

-- Data Before
SELECT * From #TableA
SELECT * From #TableB

DECLARE rows_to_update CURSOR
    FOR SELECT ID FROM @IDsToUpsert

DECLARE @rowToUpdate INT
DECLARE @existingIDB INT

OPEN rows_to_update;

FETCH NEXT FROM rows_to_update 
INTO @rowToUpdate;

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRANSACTION

        IF NOT EXISTS 
        (
            SELECT 1 FROM #TableA WITH (UPDLOCK, ROWLOCK, HOLDLOCK)
            WHERE IDA = @rowToUpdate            
        )
        BEGIN
            -- Insert into B, then insert new val into A
            INSERT INTO #TableB
            OUTPUT INSERTED.IDB, INSERTED.DataB 
            INTO #TableA
            SELECT @NewData
            -- Change code here if PK on TableB is not autogenerated
        END
        ELSE
        BEGIN
            -- Update
            UPDATE #TableA
            SET DataA = @NewData
            WHERE IDA = @rowToUpdate
        END

    COMMIT TRANSACTION

    FETCH NEXT FROM rows_to_update 
    INTO @rowToUpdate;
END

CLOSE rows_to_update;
DEALLOCATE rows_to_update;

SELECT * FROM #TableA
SELECT * FROM #TableB

DROP TABLE #TableA
DROP TABLE #TableB

【讨论】:

  • 我认为这是一个非常好的答案;这张
【解决方案2】:

回答您的一般性问题 - “我可以在一个可以嵌套在其他语句中的语句中插入和返回 PK” - 是的,绝对可以。但这取决于创建 PK 背后的逻辑。在这种情况下,似乎要生成一个 PK,您需要插入到不同的表中,然后从那里获取新生成的 ID。除非有非常具体的原因,否则这不是很有效(恕我直言)。自动增量、GUID 等倾向于作为 PK 更好地工作。如果您可以简化/更改其背后的逻辑,并且您可以找到一种更简单的方法来实现这一点,那么 PK 'CAN' 可以在一个语句/函数中生成,从而可以在其他语句中使用。

【讨论】:

    猜你喜欢
    • 2012-06-30
    • 1970-01-01
    • 2016-07-13
    • 2019-07-07
    • 2012-08-19
    • 2021-05-27
    • 2014-01-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多