复杂的TSQL合并

Geo*_*rge 5 t-sql sql-server merge upsert sql-server-2008

我已经" 继承了 "一条出色的TSQL代码:

  • 在游标上逐行循环.
  • 游标包含表A中需要合并(Upserted)的数据
  • 对于游标中的每个行循环,调用存储过程.过程:
    • 如果表A中存在相应的行,则更新它
    • 如果这样的行不存在则:
      • 在另一个表B中插入一行.
      • 获取新生成的ID(比如称为IDB)
      • 在表A中插入一行.表A插入需要一个IDB(该字段不为null,它应该只有表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
Run Code Online (Sandbox Code Playgroud)

还尝试了各种方法,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
Run Code Online (Sandbox Code Playgroud)

有没有人对此有所了解?基本上我认为如果我们推广这个问题将是:

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

提前感谢您的回复

乔治

Gal*_*llo 3

如果您在 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
Run Code Online (Sandbox Code Playgroud)