sql-server - TSQL-Merge 中条件匹配时更新和插入

标签 sql-server stored-procedures sql-server-2014 sql-merge

我一直在尝试编写一个存储过程,在其中我可以使用具有以下条件的 Merge 执行 UpSert

  • 如果记录存在,则将目标的结束日期更改为昨天,即今天 - 1
  • 如果记录不存在,则插入新记录

  • 这是我在 SP 中使用的表 tblEmployee
    CREATE TABLE tblEmployee
    (
        [EmployeeID] [int] IDENTITY(1,1) NOT NULL, 
        [Name] [varchar](10) NOT NULL,  
        [StartDate] [date] NOT NULL,
        [EndDate] [date] NOT NULL
    )
    

    这是我的 SP,它以 UDTT 作为输入参数
    CREATE PROCEDURE [dbo].[usp_UpsertEmployees]
    @typeEmployee typeEmployee READONLY -- It has same column like tblEmployye except EmployeeID
    AS
    BEGIN
        SET NOCOUNT ON;      
    
        MERGE INTO tblEmployee AS TARGET
        USING @typeEmployee AS SOURCE
        ON TARGET.Name = SOURCE.Name 
    
        WHEN MATCHED and TARGET.StartDate < SOURCE.StartDate
        THEN 
    
                --First Update Existing Record EndDate to Previous Date as shown below 
                UPDATE 
                set TARGET.EndDate = DATEADD(day, -1, convert(date, SOURCE.StartDate))
    
                -- Now Insert New Record 
                --INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate);
    
        WHEN NOT MATCHED by TARGET 
        THEN
                INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate);
    
        SET NOCOUNT OFF;        
    END
    

    当列匹配时,我如何执行更新现有记录和添加新记录

    请有人向我解释 TSQL 中合并的执行流程,即,
        WHEN MATCHED --Will this Execute Everytime
    
        WHEN NOT MATCHED by TARGET -- Will this Execute Everytime
    
        WHEN NOT MATCHED by SOURCE -- Will this Execute Everytime
    

    每次合并时都会执行以上 3 个条件还是每次只执行匹配条件

    提前致谢

    最佳答案

    这不是什么MERGE是为了做(在同一个子句中更新和插入)。为此,您可以使用 OUTPUT子句仅获取所有更新的记录。 MERGE/OUTPUT组合非常挑剔。您的 OUTPUT更新实际上是更新的 TARGET 记录,因此您必须在 temp/table 变量中启动 TARGET 记录。然后将它们与 SOURCE 匹配以执行 INSERT。您将不被允许将输出结果直接连接回源,甚至不能用作 WHERE 中的相关子查询。 .

    设置一些示例数据

    下面的代码只是设置了一些示例数据。

    -- Setup sample data
    DECLARE @typeEmployee TABLE (
        [Name] [varchar](10) NOT NULL,  
        [StartDate] [date] NOT NULL,
        [EndDate] [date] NOT NULL
    )
    DECLARE @tblEmployee TABLE (
        [EmployeeID] [int] IDENTITY(1,1) NOT NULL, 
        [Name] [varchar](10) NOT NULL,  
        [StartDate] [date] NOT NULL,
        [EndDate] [date] NOT NULL   
    )
    INSERT @tblEmployee VALUES ('Emp A', '1/1/2016', '2/1/2016')
    INSERT @typeEmployee VALUES ('Emp A', '1/5/2016', '2/2/2016'), ('Emp B', '3/1/2016', '4/1/2016')
    

    存储过程更新

    您可以使用 OUTPUT末尾有MERGE让它返回目标记录的修改记录,并包含$action ,您还将了解它是插入、更新还是删除。

    但是,结果集来自 MERGE/OUTPUT不能直接连接到 SOURCE 表,所以你可以做你的 INSERT因为你只能取回 TARGET 记录。您不能使用 OUTPUT 的结果在来自 SOURCE 表的相关子查询中。最简单的方法是使用临时表或表变量来捕获输出。
    -- Logic to do upsert
    DECLARE @Updates TABLE (
        [Name] [varchar](10) NOT NULL,  
        [StartDate] [date] NOT NULL,
        [EndDate] [date] NOT NULL
    )
    
    INSERT @Updates
        SELECT
            Name,
            StartDate,
            EndDate
        FROM (
            MERGE INTO @tblEmployee AS TARGET
            USING @typeEmployee AS SOURCE
                ON TARGET.Name = SOURCE.Name 
            WHEN MATCHED AND TARGET.StartDate < SOURCE.StartDate
            THEN
                --First Update Existing Record EndDate to Previous Date as shown below 
                UPDATE SET
                    EndDate = DATEADD(DAY, -1, CONVERT(DATE, SOURCE.StartDate))
            WHEN NOT MATCHED BY TARGET -- OR MATCHED AND TARGET.StartDate >= SOURCE.StartDate -- Handle this case?
            THEN
                INSERT VALUES(SOURCE.Name, SOURCE.StartDate, SOURCE.EndDate)
            OUTPUT $action, INSERTED.Name, INSERTED.StartDate, INSERTED.EndDate
            -- Use the MERGE to return all changed records of target table
        ) AllChanges (ActionType, Name, StartDate, EndDate)
        WHERE AllChanges.ActionType = 'UPDATE' -- Only get records that were updated
    

    现在您已经捕获了 MERGE 的输出并过滤为仅获取更新的 TARGET 记录,然后您就可以做您未完成的工作 INSERT通过仅过滤属于 MERGE 的 SOURCE 记录更新。
    INSERT @tblEmployee
        SELECT
            SOURCE.Name,
            SOURCE.StartDate,
            SOURCE.EndDate
        FROM @typeEmployee SOURCE
        WHERE EXISTS (
            SELECT *
            FROM @Updates Updates
            WHERE Updates.Name = SOURCE.Name
                -- Other join conditions to ensure 1:1 match against SOURCE (start date?)
        )
    

    输出

    这是更改后示例记录的输出。您预期的 TARGET 更改已完成。
    -- Show output
    SELECT * FROM @tblEmployee
    

    关于sql-server - TSQL-Merge 中条件匹配时更新和插入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38328940/

    相关文章:

    php - Mysql 存储过程和 Php

    SQL Case When - 从输出中删除 'empty' 行

    sql - 在 SQL Server 中将位数据类型转换为 VarChar

    sql-server - 如何从Azure VM下载100Gb文件?

    SQL - 在另一个存储过程中使用存储过程的结果

    带有游标和 View 的 MySQL 递归存储过程不刷新

    sql-server - AppDomain 已卸载

    sql-server - 从 sql server 链接服务器连接到 Sphinx

    sql-server - 报告定义具有无效的目标命名空间 rsInvalidReportDefinition

    c# - 从现有值 使用 SQL Server 中的逗号分隔值更新列