【问题标题】:Update and Insert When Condition is Matched in TSQL-Merge在 TSQL-Merge 中条件匹配时更新和插入
【发布时间】:2016-11-14 16:46:42
【问题描述】:

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

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

  2. 如果记录不存在,则插入新记录

这是我在 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
)

这是我的以 UDTT 作为输入参数的 SP

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

Merge 中每次都会执行以上 3 个条件还是每次只执行匹配条件

提前致谢

【问题讨论】:

  • when matched then begin /* update and insert here */ end...
  • 出现编译错误“'Begin' 附近的语法不正确。需要 DELETE、INSERT 或 UPDATE。
  • 好吧,我想我错了……

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


【解决方案1】:

这不是MERGE 的本意(更新和插入在同一个子句中)。为此,您可以使用OUTPUT 子句仅获取所有更新的记录。 MERGE/OUTPUT 组合非常挑剔。您的OUTPUT 更新实际上是已更新的目标记录,因此您必须在临时/表变量中启动目标记录。然后你将它们与 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')

存储过程更新

您可以在MERGE 的末尾使用OUTPUT 使其返回目标记录的修改记录,并且通过包含$action,您还将获得它是插入、更新还是删除.

但是,MERGE / OUTPUT 的结果集不能直接与 SOURCE 表连接,因此您可以执行 INSERT,因为您只能取回 TARGET 记录。您也不能在来自 SOURCE 表的相关子查询中使用 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

现在您已经捕获了MERGE 的输出并进行了过滤以仅获取更新的目标记录,然后您可以通过仅过滤作为MERGE 更新一部分的源记录来完成您未完成的INSERT

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

输出

这是更改后的示例记录的输出。您的预期目标更改已完成。

-- Show output
SELECT * FROM @tblEmployee

【讨论】:

  • 一点解释可能对您的上述代码有所帮助
  • 添加了额外的解释。你有什么具体的问题吗?很高兴解释它的任何部分......
  • 一条评论,您实际上可以插入到您要合并的表中,而无需额外的 @Updates 内存表。
【解决方案2】:

按照接受的答案的想法,这在 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'

您可以使用不同的值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 2012-12-05
    • 2017-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多