【问题标题】:SQL Server Merge, how to subset target by date range (year)SQL Server Merge,如何按日期范围(年)对目标进行子集化
【发布时间】:2016-10-21 06:11:10
【问题描述】:

我一直在阅读 SQL Server 中的 MERGE 语法,它非常适合我需要它做的事情,但是我终其一生都无法弄清楚如何防止目标表删除我没有删除的旧数据'不想匹配。

我在 Target 中有 1100 万行,我只想匹配当前年份的大约 300k 行的更改。显然会有巨大的性能差异。

我的代码:

    MERGE  [dbo].[RE_Gifts_Backup_testing] as Target   --[TARGET is main table]
    USING  [dbo].[RE_Gifts_CY_Changes] as Source  --[SOURCE is data with new changes]

    ON (Target.gift_id = Source.gift_id)  -- What are we matching rows on.




    --When rows are matched, update the records if there is any change

    WHEN MATCHED
            AND year(Target.gift_date) = '2016'
            AND year(Source.Gift_Date) = '2016' 
            AND (TARGET.[Constituent_ID] <> SOURCE.[Constituent_ID]
            OR  TARGET.[RE_Gift_ID] <> SOURCE.[RE_Gift_ID] 
            OR  TARGET.[Gift_ID] <> SOURCE.[Gift_ID] 
            OR  TARGET.[Gift_Date_Added] <> SOURCE.[Gift_Date_Added] 
            OR  TARGET.[Gift_Date] <> SOURCE.[Gift_Date] 
            OR  TARGET.[Name] <> SOURCE.[Name] 
            OR  TARGET.[Gift_Type] <> SOURCE.[Gift_Type] 
            OR  TARGET.[Gift_Amount] <> SOURCE.[Gift_Amount] 
            OR  TARGET.[Frequency] <> SOURCE.[Frequency] 
            OR  TARGET.[Pay_Method] <> SOURCE.[Pay_Method]
            OR  TARGET.[Appeal] <> SOURCE.[Appeal]
            OR  TARGET.[Campaign] <> SOURCE.[Campaign]
            OR  TARGET.[Gift Added By] <> SOURCE.[Gift Added By]
            OR  TARGET.[Gift Reference] <> SOURCE.[Gift Reference]
            OR  TARGET.[SoftCredit] <> SOURCE.[SoftCredit]
            OR  TARGET.[Relationship Manager] <> SOURCE.[Relationship Manager]
            OR  TARGET.[CostCentre] <> SOURCE.[CostCentre] 
            OR  TARGET.[Recruiter] <> SOURCE.[Recruiter] 
            OR  TARGET.[Sitecode] <> SOURCE.[Sitecode] 
            OR  TARGET.[Zerodebit] <> SOURCE.[Zerodebit] 
            OR  TARGET.[Donation_Channel] <> SOURCE.[Donation_Channel] 
            OR  TARGET.[Source_Channel] <> SOURCE.[Source_Channel] 
            OR  TARGET.[Recruitment_Source] <> SOURCE.[Recruitment_Source] 
            OR  TARGET.[Merch_ProductID] <> SOURCE.[Merch_ProductID] )


        THEN UPDATE 
            set 
              TARGET.[Constituent_ID] = SOURCE.[Constituent_ID],
              TARGET.[RE_Gift_ID] = SOURCE.[RE_Gift_ID],
              TARGET.[Gift_ID] = SOURCE.[Gift_ID], 
              TARGET.[Gift_Date_Added] = SOURCE.[Gift_Date_Added], 
              TARGET.[Gift_Date] = SOURCE.[Gift_Date], 
              TARGET.[Name] = SOURCE.[Name],
              TARGET.[Gift_Type] = SOURCE.[Gift_Type],
              TARGET.[Gift_Amount] = SOURCE.[Gift_Amount],
              TARGET.[Frequency] = SOURCE.[Frequency],
              TARGET.[Pay_Method] = SOURCE.[Pay_Method],
              TARGET.[Appeal] = SOURCE.[Appeal],
              TARGET.[Campaign] = SOURCE.[Campaign],
              TARGET.[Gift Added By] = SOURCE.[Gift Added By],
              TARGET.[Gift Reference] = SOURCE.[Gift Reference],
              TARGET.[SoftCredit] = SOURCE.[SoftCredit],
              TARGET.[Relationship Manager] = SOURCE.[Relationship Manager],
              TARGET.[CostCentre] = SOURCE.[CostCentre],
              TARGET.[Recruiter] = SOURCE.[Recruiter],
              TARGET.[Sitecode] = SOURCE.[Sitecode],
              TARGET.[Zerodebit] = SOURCE.[Zerodebit],
              TARGET.[Donation_Channel] = SOURCE.[Donation_Channel],
              TARGET.[Source_Channel] = SOURCE.[Source_Channel],
              TARGET.[Recruitment_Source] = SOURCE.[Recruitment_Source],
              TARGET.[Merch_ProductID] = SOURCE.[Merch_ProductID]

    -- when no records are matched then insert from source into target.
    WHEN NOT MATCHED BY TARGET and year(target.gift_date) >= '2016' THEN 

    INSERT ([Constituent_ID],[RE_Gift_ID],[Gift_ID],[Gift_Date_Added],[Gift_Date],[Name],[Gift_Type],[Gift_Amount],[Frequency],[Pay_Method],[Appeal],[Campaign],[Gift Added By],[Gift Reference],
                [SoftCredit],[Relationship Manager],[CostCentre],[Recruiter],[Sitecode],[Zerodebit],[Donation_Channel],[Source_Channel],[Recruitment_Source],[Merch_ProductID])

    VALUES (source.[Constituent_ID],source.[RE_Gift_ID],source.[Gift_ID],source.[Gift_Date_Added],source.[Gift_Date],source.[Name],source.[Gift_Type],source.[Gift_Amount],source.[Frequency],
                source.[Pay_Method],source.[Appeal],source.[Campaign],source.[Gift Added By],source.[Gift Reference],source.[SoftCredit],source.[Relationship Manager],source.[CostCentre],
                source.[Recruiter],source.[Sitecode],source.[Zerodebit],source.[Donation_Channel],source.[Source_Channel],source.[Recruitment_Source],source.[Merch_ProductID])

    --When there is a row that exists in target table and same record does not exist in source table then delete this record from target table
    WHEN NOT MATCHED BY SOURCE and year(target.gift_date) >= '2016' THEN 
    DELETE

    OUTPUT $action, 
            deleted.[Constituent_ID] as [deletedConstituent_ID],
            deleted.[RE_Gift_ID] as [deletedRE_Gift_ID],
            deleted.[Gift_ID] as [deletedGift_ID], 
            deleted.[Gift_Date_Added] as [deletedGift_Date_Added], 
            deleted.[Gift_Date] as [deletedGift_Date], 
            deleted.[Name] as [deletedName],
            deleted.[Gift_Type] as [deletedGift_Type],
            deleted.[Gift_Amount] as [deletedGift_Amount],
            deleted.[Frequency] as [deletedFrequency],
            deleted.[Pay_Method] as [deletedPay_Method],
            deleted.[Appeal] as [deletedAppeal],
            deleted.[Campaign] as [deletedCampaign],
            deleted.[Gift Added By] as [deletedGift Added By],
            deleted.[Gift Reference] as [deletedGift Reference],
            deleted.[SoftCredit] as [deletedSoftCredit],
            deleted.[Relationship Manager] as [deletedRelationship Manager],
            deleted.[CostCentre] as [deletedCostCentre],
            deleted.[Recruiter] as [deletedRecruiter],
            deleted.[Sitecode] as [deletedSitecode],
            deleted.[Zerodebit] as [deletedZerodebit],
            deleted.[Donation_Channel] as [deletedDonation_Channel],
            deleted.[Source_Channel] as [deletedSource_Channel],
            deleted.[Recruitment_Source] as [deletedRecruitment_Source],
            deleted.[Merch_ProductID] as [deletedMerch_ProductID],

            inserted.[Constituent_ID] as [insertedConstituent_ID],
            inserted.[RE_Gift_ID] as [insertedRE_Gift_ID],
            inserted.[Gift_ID] as [insertedGift_ID], 
            inserted.[Gift_Date_Added] as [insertedGift_Date_Added], 
            inserted.[Gift_Date] as [insertedGift_Date], 
            inserted.[Name] as [insertedName],
            inserted.[Gift_Type] as [insertedGift_Type],
            inserted.[Gift_Amount] as [insertedGift_Amount],
            inserted.[Frequency] as [insertedFrequency],
            inserted.[Pay_Method] as [insertedPay_Method],
            inserted.[Appeal] as [insertedAppeal],
            inserted.[Campaign] as [insertedCampaign],
            inserted.[Gift Added By] as [insertedGift Added By],
            inserted.[Gift Reference] as [insertedGift Reference],
            inserted.[SoftCredit] as [insertedSoftCredit],
            inserted.[Relationship Manager] as [insertedRelationship Manager],
            inserted.[CostCentre] as [insertedCostCentre],
            inserted.[Recruiter] as [insertedRecruiter],
            inserted.[Sitecode] as [insertedSitecode],
            inserted.[Zerodebit] as [insertedZerodebit],
            inserted.[Donation_Channel] as [insertedDonation_Channel],
            inserted.[Source_Channel] as [insertedSource_Channel],
            inserted.[Recruitment_Source] as [insertedRecruitment_Source],
            inserted.[Merch_ProductID] as [insertedMerch_ProductID],

            updated.[Constituent_ID] as [updatedConstituent_ID],
            updated.[RE_Gift_ID] as [updatedRE_Gift_ID],
            updated.[Gift_ID] as [updatedGift_ID], 
            updated.[Gift_Date_Added] as [updatedGift_Date_Added], 
            updated.[Gift_Date] as [updatedGift_Date], 
            updated.[Name] as [updatedName],
            updated.[Gift_Type] as [updatedGift_Type],
            updated.[Gift_Amount] as [updatedGift_Amount],
            updated.[Frequency] as [updatedFrequency],
            updated.[Pay_Method] as [updatedPay_Method],
            updated.[Appeal] as [updatedAppeal],
            updated.[Campaign] as [updatedCampaign],
            updated.[Gift Added By] as [updatedGift Added By],
            updated.[Gift Reference] as [updatedGift Reference],
            updated.[SoftCredit] as [updatedSoftCredit],
            updated.[Relationship Manager] as [updatedRelationship Manager],
            updated.[CostCentre] as [updatedCostCentre],
            updated.[Recruiter] as [updatedRecruiter],
            updated.[Sitecode] as [updatedSitecode],
            updated.[Zerodebit] as [updatedZerodebit],
            updated.[Donation_Channel] as [updatedDonation_Channel],
            updated.[Source_Channel] as [updatedSource_Channel],
            updated.[Recruitment_Source] as [updatedRecruitment_Source],
            updated.[Merch_ProductID] as [updatedMerch_ProductID];



    SELECT @@ROWCOUNT;
    GO

这会引发错误:

消息 5334,第 16 级,状态 2,第 86 行
无法绑定标识符“target.gift_date”。 MERGE 语句的“WHEN NOT MATCHED BY SOURCE”子句中只允许目标列和子句范围内的列。

【问题讨论】:

    标签: sql sql-server merge subset


    【解决方案1】:

    这是一个完整的脚本,演示了我将如何做到这一点。已在 SQL Server 2008 中检查。

    样本数据

    DECLARE @TDst TABLE (ID int, dt date, DataValue int);
    DECLARE @TSrc TABLE (ID int, dt date, DataValue int);
    
    INSERT INTO @TDst (ID, dt, DataValue) VALUES
     (11, '2015-01-01', 1100) -- don't delete
    ,(12, '2015-02-02', 1200) -- don't delete
    ,(21, '2016-01-01', 2100) -- this should be deleted
    ,(22, '2016-02-02', 2200) -- this would remain as is
    ,(23, '2016-03-03', 2300) -- this would be updated
    ;
    
    INSERT INTO @TSrc (ID, dt, DataValue) VALUES
     (22, '2016-02-02', 2200) -- same date and value, don't update
    ,(23, '2016-03-03', 2388) -- update
    ,(24, '2016-04-04', 2488) -- add
    ;
    
    SELECT * FROM @TDst;
    
    +----+------------+-----------+
    | ID |     dt     | DataValue |
    +----+------------+-----------+
    | 11 | 2015-01-01 |      1100 |
    | 12 | 2015-02-02 |      1200 |
    | 21 | 2016-01-01 |      2100 |
    | 22 | 2016-02-02 |      2200 |
    | 23 | 2016-03-03 |      2300 |
    +----+------------+-----------+
    

    查询

    MERGE INTO @TDst AS Dst
    USING @TSrc as Src
    ON (Dst.ID = Src.ID)
    
    --When rows are matched, update the records if there is any change
    WHEN MATCHED
        AND (Dst.dt <> Src.dt 
        OR Dst.DataValue <> Src.DataValue)
    THEN 
    UPDATE 
    SET
         Dst.dt = Src.dt
        ,Dst.DataValue = Src.DataValue
    
    -- when no records are matched then insert from source into target.
    WHEN NOT MATCHED BY TARGET THEN
    INSERT (ID, dt, DataValue)
    VALUES (Src.ID, Src.dt, Src.DataValue)
    
    -- When there is a row that exists in target table and 
    -- same record does not exist in source table then delete this record from target table
    WHEN NOT MATCHED BY SOURCE AND Dst.dt >= '2016-01-01' THEN 
    DELETE
    ;
    
    SELECT * FROM @TDst;
    

    结果

    +----+------------+-----------+
    | ID |     dt     | DataValue |
    +----+------------+-----------+
    | 11 | 2015-01-01 |      1100 |
    | 12 | 2015-02-02 |      1200 |
    | 22 | 2016-02-02 |      2200 |
    | 23 | 2016-03-03 |      2388 |
    | 24 | 2016-04-04 |      2488 |
    +----+------------+-----------+
    

    如您所见,2016 年之前的数据被保留,但 2016 年的数据被替换。

    如果您的 Source 表仅包含 2016 年的数据,那么您需要在 WHEN NOT MATCHED BY SOURCE 子句中添加额外的过滤器 &gt;= '2016-01-01' 以防止删除 2016 年之前的行。


    您遇到的错误很可能是由以下不需要的额外过滤器引起的:

    WHEN NOT MATCHED BY TARGET and year(target.gift_date) >= '2016'
    

    【讨论】:

    • 感谢弗拉基米尔,这很有帮助。事实证明,您发现的最后一行是问题,因为我列出了 Target 应该是它的来源。
    猜你喜欢
    • 2016-09-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-12
    • 1970-01-01
    • 2018-10-02
    • 2013-12-18
    • 1970-01-01
    • 2018-12-24
    相关资源
    最近更新 更多