【问题标题】:Why WHEN MATCHED' cannot appear more than once in a 'UPDATE' clause of a MERGE statement?为什么 WHEN MATCHED 不能在 MERGE 语句的 'UPDATE' 子句中出现多次?
【发布时间】:2016-04-30 21:02:43
【问题描述】:

我正在执行一堆 CRUD 操作并从 CUD 中创建合并存储过程。我的存储过程是这样的

CREATE PROCEDURE usp_AdministrationHistoryMerge
    @AdministrationHistoryID int out,
    @AdministratorID int,
    @DateCreated datetime,
    @CreatedBy nvarchar(50),
    @ModifiedBy nvarchar(50),
    @Action int
AS

SET NOCOUNT OFF
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

DECLARE @ERROR_SEVERITY int,
        @MESSAGE varchar(1000),
        @ERROR_NUMBER int,
        @ERROR_PROCEDURE nvarchar(200),
        @ERROR_LINE int,
        @ERROR_MESSAGE nvarchar(4000),
        @IsActive bit,
        @DateModified datetime;
begin try
    if @Action = 1
        begin
            set @IsActive = 1
            set @AdministrationHistoryID = SCOPE_IDENTITY()
        end
    merge [AdministrationHistory] as target
    using (select @AdministratorID, @DateCreated, @CreatedBy, @DateModified, @ModifiedBy, @IsActive)
    as source (AdministratorID, DateCreated, CreatedBy, DateModified, ModifiedBy, IsActive)
    on (target.AdministrationHistoryID = source.AdministrationHistoryID)
    when matched and @Action = -1 then
        update
            set IsActive = 0
    when matched and @Action = 0 then
        update
        set ModifiedBy = @ModifiedBy,
        DateModified = GETDATE()
    when matched and @Action = 1 then
    insert
    (AdministratorID, DateCreated, CreatedBy, IsActive)
    values
    (@AdministratorID, @DateCreated, @CreatedBy, @IsActive);
end try

BEGIN CATCH
    SET @ERROR_SEVERITY = ISNULL(ERROR_SEVERITY(),'');
    SET @ERROR_NUMBER = ISNULL(ERROR_NUMBER(),'');
    SET @ERROR_PROCEDURE = ISNULL(ERROR_PROCEDURE(),''); 
    SET @ERROR_LINE = ISNULL(ERROR_LINE(),'');
    SET @ERROR_MESSAGE = ISNULL(ERROR_MESSAGE(),'');

    -- Test if the transaction is uncommittable.
    IF (XACT_STATE()) = -1
        BEGIN
            --PRINT N'The transaction is in an uncommittable state. Rolling back transaction.'
            ROLLBACK TRANSACTION;
        END;

    -- Test if the transaction is active and valid.
    IF (XACT_STATE()) = 1
        BEGIN
            --PRINT N'The transaction is committable. Committing transaction.'
            COMMIT TRANSACTION;   
        END;

    SET @MESSAGE = 'Error Occured in Stored Procedure ' + cast(@ERROR_PROCEDURE as varchar(200)) + 
                    '; Line Number ' + cast(@ERROR_LINE as varchar) + 
                    '; Message: [' + cast(@ERROR_NUMBER as varchar) + '] - '
                    + cast(@ERROR_MESSAGE as varchar(255))

    RAISERROR(@MESSAGE, @ERROR_SEVERITY, 1);
END CATCH;

当我去执行这个时,我得到了这个完整的错误

消息 10714,级别 15,状态 1,过程 usp_AdministrationHistoryMerge,第 36 行 “WHEN MATCHED”类型的操作不能在 MERGE 语句的“UPDATE”子句中出现多次。

我查看了 SO 并找到了几种解决此问题的方法,但我发现不适用于此错误的解决方案,而不是删除,我需要将记录的 IsActive 更新为 0。

另外,在我的搜索中,没有人真正解释为什么会抛出这个错误,是的,我知道它很明显,因为错误就在那里,但为什么不允许发生这种情况?基于这种情况,有什么想法可以做到这一点吗?还是当@Action 为0 时我应该让这个合并调用另一个存储过程?

【问题讨论】:

  • 您确定要使用MERGE 吗? mssqltips.com/sqlservertip/3074/…
  • documentation on MERGE中明确说明:MERGE语句最多可以有两个WHEN MATCHED子句。 * *如果有两个 WHEN MATCHED 子句,那么一个必须指定一个 UPDATE 操作,一个必须指定一个 DELETE 操作。
  • @AaronBertrand,使用 MERGE 不是我的选择,我认为使用 CASE 或 IF 语句会是更好的选择...顺便说一句,文章不错
  • @TT,也许你可以发表你的评论作为答案,如果其他人有同样的问题,而不是 DBA 或通常使用 SQL 的人,可能会受益。
  • @TT.:完全可以在 SQL Server 中使用多个 WHEN MATCHED THEN UPDATE 子句来模拟标准 SQL MERGE。只是比较费力。

标签: sql sql-server merge


【解决方案1】:

在您的 MERGE 语句中,您有三个 WHEN MATCHED 子句

  • 两个带有UPDATE 声明
  • 带有INSERT 声明的一个。

但是,这是不允许的。在Documentation on MERGE中有明确说明:

MERGE 语句最多可以有两个WHEN MATCHED 子句。

还有

如果有两个WHEN MATCHED 子句,则一个必须指定UPDATE 操作,一个必须指定DELETE 操作。

同样重要的是要知道:

如果在 子句中指定了UPDATE,并且根据 的多行匹配 target_table 中的一行,则 SQL Server 返回错误。 MERGE 语句不能多次更新同一行,也不能更新和删除同一行。

【讨论】:

    【解决方案2】:

    SQL 标准(例如 Db2)允许这样做,但 SQL Server 不允许。 This article documents how you can emulate several WHEN MATCHED THEN UPDATE clauses also in SQL Server。你的情况

    MERGE INTO [AdministrationHistory] AS target
    USING (
      SELECT @AdministratorID, @DateCreated, @CreatedBy, @DateModified, @ModifiedBy, @IsActive
    ) AS source (AdministratorID, DateCreated, CreatedBy, DateModified, ModifiedBy, IsActive)
    ON (target.AdministrationHistoryID = source.AdministrationHistoryID)
    
    -- Combine both predicates using OR. This is optional.
    WHEN MATCHED AND @Action = -1 OR @Action = 0 THEN UPDATE SET
    
      -- UPDATE clause from the first "WHEN MATCHED AND @Action = -1" clause
      IsActive = CASE WHEN @Action = -1 THEN 0 ELSE IsActive END,
    
      -- UPDATE clauses from the second "WHEN MATCHED AND @Action = 0" clause
      ModifiedBy = CASE WHEN @Action = 0 THEN @ModifiedBy ELSE ModifiedBy END,
      DateModified = CASE WHEN @Action = 0 THEN GETDATE() ELSE DateModified END
    
    WHEN NOT MATCHED AND @Action = 1 THEN INSERT
        (AdministratorID, DateCreated, CreatedBy, IsActive)
      VALUES 
        (@AdministratorID, @DateCreated, @CreatedBy, @IsActive);
    

    【讨论】:

      猜你喜欢
      • 2022-07-06
      • 1970-01-01
      • 1970-01-01
      • 2018-02-18
      • 2016-05-10
      • 2020-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多