Update and Insert When Condition is Matched in TSQL-Merge

こ雲淡風輕ζ 提交于 2019-12-05 20:26:35

This isn't what MERGE is meant to do (update and insert in same clause). To accomplish this, you can use the OUTPUT clause to get all the updated records only. The MERGE/OUTPUT combo is very picky. Your OUTPUT updates are really the TARGET records that got updated, so you have to start the TARGET records in a temp/table variable. Then you match those back against the SOURCE to do the INSERT. You won't be allowed to join the output results directly back to source or even use as a correlated subquery within the WHERE.

Setup some sample data

The code below just sets up some sample data.

-- 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')

Updates to Stored Procedure

You can use OUTPUT at the end of a MERGE to have it return the modified records of the target records, and by including $action, you will also get whether it was an insert, update, or delete.

However, the result set from MERGE / OUTPUT cannot be directly joined against the SOURCE table so you can do your INSERT since you only get the TARGET records back. You can't use the results of the OUTPUT within correlated sub-query from the SOURCE table either. Easiest thing is to use a temp table or table variable to capture the output.

-- 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

Now that you've captured the output of the MERGE and filtered to only get updated TARGET records, you can then do your outstanding INSERT by filtering only the SOURCE records that were part of the MERGE update.

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?)
    )

Ouput

This is the output of the sample records after the change. Your intended TARGET changes were made.

-- Show output
SELECT * FROM @tblEmployee

Following the idea from the accepted answer, this works as well in Sql server 2008 r2:

create table Test1 (
  Id int, FromDate date, ThruDate date, Value int
)


insert into dbo.Test1
 (Id, FromDate, ThruDate, [Value])
select 
  t.Id, t.FromDate, T.ThruDate, t.Value * 100
from (

MERGE  dbo.Test1 AS Target
 USING (
         select 1 as Id, '2000-01-01' as FromDate, '2000-12-31' as ThruDate, 2 as Value
       ) AS Source
    ON (  target.id = source.Id
        )
WHEN MATCHED
THEN
  UPDATE SET Target.[Id] = Source.[Id]
           , Target.[FromDate] = Source.[FromDate]
           , Target.[ThruDate] = Source.[ThruDate]
           , Target.[Value] = Source.[Value]
WHEN NOT MATCHED BY TARGET
THEN
  INSERT ([Id]
        , [FromDate]
        , [ThruDate]
        , [Value])
  VALUES (Source.[Id]
        , Source.[FromDate]
        , Source.[ThruDate]
        , Source.[Value])
OUTPUT $ACTION as Act, Inserted.*
) t
where t.Act  = 'Update'

You can play with different values.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!