【问题标题】:How to insert new rows and mark existing rows with no update and updated rows in SQL Server using merge如何使用合并在 SQL Server 中插入新行并标记没有更新和更新行的现有行
【发布时间】:2021-12-16 00:34:29
【问题描述】:

我需要在 SQL Server 中创建一个存储过程并实现一个 upsert,以便它将数据从临时表(又名源)移动到最终表(又名目标)并标记新的、更新的、未更新的行或每次新一批数据进来时都被删除。我按照here的解释使用合并。

问题在于它正在更新没有任何更改的行。我的工作流程如下:

  1. 将数据加载到源表中
  2. 调用存储过程,根据合并条件将数据从Source 移动到Target

我的存储过程如下:

CREATE PROCEDURE [dbo].[upsert_with_flag_2]
AS
    DECLARE @current_time AS datetime
    SET @current_time = GETDATE()

    MERGE [dbo].[employee] AS Target
    USING [dbo].[employee_staging] AS Source
          ON Source.[first_name] = Target.[first_name] 
             AND Source.[last_name] = Target.[last_name] 
             AND Source.[dob] = Target.[dob]

    WHEN MATCHED
        THEN
            UPDATE 
            SET Target.[salary] = Source.[salary],
                Target.[current_address] = Source.[current_address],
                Target.[is_deleted] = 'Updated',
                Target.[processed_date] = @current_time

    WHEN NOT MATCHED BY Target 
        THEN
            INSERT ([first_name], [last_name], 
                    [dob], [salary], 
                    [current_address], [is_deleted], 
                    [processed_date])
            VALUES (Source.[first_name], Source.[last_name],
                    Source.[dob], Source.[salary], 
                    Source.[current_address], 'New',
                    @current_time);

-- After doing upsert, check for rows whose processed date is less than current date but status is new or updated, These are
-- the rows which were not present in input file. Update there status to deleted
-- QUESTION: Should we change the processed date to current date for row's whose status is deleted?

UPDATE [dbo].[employee]
SET [is_deleted] = 'deleted'
WHERE ([is_deleted] = 'New' OR [is_deleted] = 'Updated') 
  AND [processed_date] < @current_time

在此之后,我执行以下步骤来加载数据并获取输出:

--Loading the initial data
TRUNCATE TABLE [dbo].[employee_staging]
GO

INSERT INTO [dbo].[employee_staging] ([first_name], 
                                      [last_name], 
                                      [dob], 
                                      [salary], 
                                      [current_address])
VALUES  ('John', 'Doe', '1995-04-28', 3000, 'Andra Pradesh'),
        ('Robert', 'Spenser', '1994-03-28', 1800, 'Madhya Pradesh'),
        ('Vikash', 'Sharma', '1996-12-20', 1400, 'Uttar Pradesh'),
        ('Anup', 'Soni', '1994-03-28', 1800, 'Delhi'),
        ('Prijan', 'Sonar', '1989-01-28', 3000, 'Himachal Pradesh')
GO

EXEC upsert_with_flag

SELECT * FROM [dbo].[employee]

--Loading the updated data
TRUNCATE TABLE [dbo].[employee_staging]
GO

INSERT INTO [dbo].[employee_staging] ([first_name], [last_name], 
                                       [dob], [salary], 
                                       [current_address])
VALUES ('Robert', 'Spenser', '1994-03-28', 2000, 'Madhya Pradesh'),
       ('Vikash', 'Sharma', '1996-12-20', 1400, 'Maharashtra'),
       ('Anup', 'Soni', '1994-03-28', 1800, 'Delhi'),
       ('Prijan', 'Sonar', '1989-01-28', 3000, 'Himachal Pradesh'),
       ('William', 'Beck', '1991-04-22', 3300, 'Karnataka'),
       ('Robert', 'Brownie', '1986-04-22', 5000, 'Assam')

注意第 4 行和第 5 行。Anup 的输入行数据没有变化,但我仍将 [is_deleted] 列为“已更新”。我希望它类似于“现有”或“无变化”。

请帮助使这成为可能。这个 upsert 逻辑是大管道的一部分,我们需要在新文件中更新、新、未更新或删除的行。我如何做到这一点?

【问题讨论】:

    标签: sql sql-server stored-procedures


    【解决方案1】:

    您可以在更新中的 is_deleted 字段中添加 case 语句,以检查是否有任何更改。像这样的:

    MERGE [dbo].[employee] as Target
    USING [dbo].[employee_stagging] as Source
    ON Source.[first_name] = Target.[first_name] and
       Source.[last_name] = Target.[last_name] and
       Source.[dob] = Target.[dob]
    
    WHEN MATCHED
    THEN
        UPDATE 
        SET Target.[salary] = Source.[salary],
            Target.[current_address] = Source.[current_address],
            Target.[is_deleted] = CASE WHEN Source.salary = Target.salary
                                             AND Source.current_address = Target.current_address THEN 'No change'
                                       ELSE 'Updated'
                                   END,
            Target.[processed_date] = @current_time
    
    WHEN NOT MATCHED BY Target 
    THEN
        INSERT ([first_name],
                [last_name],
                [dob],
                [salary], 
                [current_address], 
                [is_deleted],
                [processed_date])
    
        VALUES (Source.[first_name],
                Source.[last_name],
                Source.[dob],
                Source.[salary], 
                Source.[current_address], 
                'New',
                @current_time
                );
    

    因此,如果更新字段与暂存数据相同,它将更新 is_deleted 为“无更改”,而如果更新字段已更改,它将更新 is_deleted到“更新”。

    注意:此代码假设薪水和 current_address 是不可为空的字段(字段的表定义后跟 NOT NULL)。如果有 ARE 可以为空,那么您应该通过将 CASE 语句替换为以下内容来处理空值:

    CASE WHEN (Source.salary = Target.salary
            OR Source.salary IS NULL
                AND Target.salary IS NULL)
        AND (Source.current_address = Target.current_address
            OR Source.current_address IS NULL
                AND Target.current_address IS NULL) THEN 'No change'
        ELSE 'Updated'
    END
    

    (文档here

    【讨论】:

    • 我收到以下错误:“'WHEN MATCHED' 类型的操作不能在 MERGE 语句的 'UPDATE' 子句中出现多次。”似乎不能多次使用 when 匹配子句。
    • 你好@Ashishsoni!对不起,我的错 :D 我确实知道!您可以使用 WHEN MATCHED 两次,但如果两者都用于 UPDATE 语句,则不能使用。如果一个用于更新,另一个用于删除(不是您想要的!),您可以使用它两次。我已更改代码以在您的 is_deleted 字段中包含一个基本的 CASE 语句,这应该可以解决问题。
    • 非常感谢@sgtwickool 这就像一个魅力。还要感谢您考虑 NULL 案例。我得到了我需要的逻辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-23
    • 1970-01-01
    • 2012-06-16
    • 1970-01-01
    相关资源
    最近更新 更多