目标不匹配时如何与更新源合并?

tos*_*ory 7 sql-server concurrency merge

我正在尝试编写一个存储过程,该过程将从临时表中获取新值,将它们合并到一个实际表中,为我提供插入行的 ID,以及与其值匹配的已经存在的行。

现在,从我所看到的来看,MERGE 函数似乎不支持我想要做的事情,除非我做错了(完全有可能,我不是 SQL 专家——.NET)。不管怎样,我今天晚上才真正了解了MERGE,一开始我似乎应该使用它,但它似乎没有我需要的功能,因为我无法获得以前存在的ID,我需要其余的存储过程。我的主要重点是看看我是否可以让它工作,但另一个问题是我需要确保代码处理对将通过 MERGE 运行的存储过程的潜在并发调用。我知道我现在没有锁定在那里,但我只是想让它在这一点上工作。那么,我所拥有的......

设置

DECLARE @Metadata TABLE
(
    MetadataId INT PRIMARY KEY IDENTITY(1,1),
    MetadataTypeId INT,
    MetadataTypeValueId INT
)

INSERT INTO @Metadata 
VALUES
(1,23),(2,32),(2,33),(2,43),(1,24),(3,33),(1,20)

--Original Table Data
SELECT * FROM @Metadata

DECLARE @MergeMetadata TABLE
(
    MetadataId INT,
    MetadataTypeId INT,
    MetadataTypeValueId INT
)

INSERT INTO @MergeMetadata (MetadataTypeId, MetadataTypeValueId)
VALUES
(2,32),(2,35),(3,34),(4,1),(1,23)

--Metadata that needs added and/or ID'd if exists
SELECT * FROM @MergeMetadata

DECLARE @FinalMetadata TABLE
(
    MetadataId INT,
    MetadataTypeId INT,
    MetadataTypeValueId INT
)

INSERT INTO @FinalMetadata (MetadataId, MetadataTypeId, MetadataTypeValueId)
VALUES
(2,2,32),(8,2,35),(9,3,34),(10,4,1),(1,1,23)

--This is the same Type/TypeValue Id's from the table above
--However, they've been ID'd with existing IDs from the original table
--Or inserted and ID'd if they didn't exist
--Order doesn't matter for the result, I just need the IDs
SELECT * FROM @FinalMetadata
Run Code Online (Sandbox Code Playgroud)

尝试 1

我首先尝试通过在匹配时插入到结果表中来通过合并获取值,并使用输出获取新值:

--Try to Insert the existing target when matched into the final table
MERGE @Metadata AS T
    USING @MergeMetadata AS S
        ON (S.MetadataTypeId = T.MetadataTypeId 
        AND S.MetadataTypeValueId = T.MetadataTypeValueId)
    WHEN MATCHED
        THEN --CANT INSERT WHEN MATCHED
            INSERT INTO (T.MetadataId, S.MetadataTypeId, S.MetadataTypeValueId) INTO @FinalMetadata
    WHEN NOT MATCHED BY TARGET
        THEN 
            INSERT (MetadataTypeId, MetadataTypeValueId) 
            VALUES (S.MetadataTypeId, S.MetadataTypeValueId)
    OUTPUT inserted.MetadataId, 
           inserted.MetadataTypeId, 
           inserted.MetadataValueTypeId,
           INTO @FinalMetadata
        ;
Run Code Online (Sandbox Code Playgroud)

尝试 2

然后我尝试使用 OUTPUT 获取原始 ID - 没有意识到它没有用于 UPDATED 的占位符 :(

--Try to get the original IDs by OUTPUT
MERGE @Metadata AS T
    USING @MergeMetadata AS S
        ON (S.MetadataTypeId = T.MetadataTypeId 
        AND S.MetadataTypeValueId = T.MetadataTypeValueId)
    WHEN MATCHED
        THEN 
            UPDATE SET (T.MetadataId = T.MetadataId)
    WHEN NOT MATCHED BY TARGET
        THEN 
            INSERT (MetadataTypeId, MetadataTypeValueId) 
            VALUES (S.MetadataTypeId, S.MetadataTypeValueId)
    OUTPUT inserted.MetadataId, 
           updated.MetadataId, --updated isn't a key for OUTPUT 
           inserted.MetadataValueTypeId,
           INTO @FinalMetadata
        ;
Run Code Online (Sandbox Code Playgroud)

尝试 3

最后,我试图逆转我的攻击计划,但我无法执行插入源

--Try to reverse logic and insert into Source when not matched
MERGE @MergeMetadata T
    USING @Metadata S
        ON (S.MetadataTypeId = T.MetadataTypeId 
        AND S.MetadataTypeValueId = T.MetadataTypeValueId)
    WHEN MATCHED
        THEN UPDATE SET T.MetadataId = S.MetadataId
    WHEN NOT MATCHED BY SOURCE
        THEN --can't insert in NOT MATCHED BY SOURCE
            INSERT (MetadataTypeId, MetadataTypeValueId) 
            VALUES (T.MetadataTypeId, T.MetadataTypeValueId) 
    ; 
Run Code Online (Sandbox Code Playgroud)

抱歉,篇幅太长,只是想表明我确实对此进行了一些思考和尝试。在这一点上,我应该写 SELECT / IF NOT EXISTS / BEGIN INSERT 经典逻辑吗?从我对 MERGE 的所有阅读来看,它听起来很适合这种情况,但是,它似乎不想给我真正需要的东西。

如果我必须走其他逻辑的路线,您能否提供一些推荐的代码片段来处理并发,以便并发执行不会同时执行相同的不存在/插入?

Pie*_*ens 3

MERGE 通过提供以下内容来跟踪更新:

  • $Action 值更新
  • 内存表中的旧值被删除;和
  • 内存表中的新值被插入

因此,在找到匹配的情况下以及在目标没有匹配的情况下,标识列的值在插入的内存表中可用。