【问题标题】:How to delete from source using MERGE command in SQL Server 2008?如何在 SQL Server 2008 中使用 MERGE 命令从源中删除?
【发布时间】:2011-09-07 09:41:03
【问题描述】:

我目前正在使用以下合并代码将日期从源迁移到目标。一旦在目标上执行更新/插入,我有一个新要求来扩展以下代码以从源中删除记录。这是否可能使用合并(我在网上看到的所有示例都在目标而不是源中执行了 del/insert/update)

    MERGE Target1 AS T
USING Source1 AS S
ON (T.EmployeeID = S.EmployeeID) 
WHEN NOT MATCHED BY TARGET AND S.EmployeeName LIKE 'S%' 
    THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
WHEN MATCHED 
    THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
    THEN DELETE  ;

【问题讨论】:

  • 您不能将其作为单个语句的一部分。每个数据修改语句只对单个表进行修改。

标签: sql-server-2008 merge


【解决方案1】:

在我们的例子中,我们想使用 MERGE 将我们的内部数据库与不同结构的外部源同步。自动 CASCADE 设置不是一种选择,因为我们喜欢许多周期性的关系,而且,实际上,我们不喜欢心怀不满的员工手中的那种廉价权力。在子行消失之前,我们无法删除父行。

所有这些都是通过使用表值参数的闪电般快速的 MERGE 完成的。到目前为止,它们以极低的应用内存开销提供了最佳性能。

为 MERGE of Orders 数据结合分散的建议...

CREATE PROCEDURE MyOrderMerge @SourceValues [MyOrderSqlUserType] READONLY
AS
BEGIN
    DECLARE @LiveRows TABLE (MergeAction VARCHAR(20), OrderId INT);
    DECLARE @DeleteCount INT;

    SET @DeleteCount = 0;

    MERGE INTO [Order] AS [target]

        USING   (   SELECT  sv.OrderNumber,
                    c.CustomerId,
                    st.ShipTypeId
                    sv.OrderDate,
                    sv.IsPriority

                    FROM @SourceValues sv
                        JOIN [Customer] c ON sv.[CustomerName] = c.[CustomerName]
                        JOIN [ShipType] st ON ...
            ) AS [stream]
            ON [stream].[OrderNumber] = [target].[SourceOrderNumber]

        WHEN MATCHED THEN
            UPDATE
                ...

        WHEN NOT MATCHED BY TARGET THEN
            INSERT
                ---

        -- Keep a tally of all active source records
        -- SQL Server's "INSERTED." prefix encompases both INSERTed and UPDATEd rows <insert very bad words here>
        OUTPUT $action, INSERTED.[OrderId] INTO @LiveRows

    ;   -- MERGE has ended

    -- Delete child OrderItem rows before parent Order rows

    DELETE FROM [OrderItem]
        FROM [OrderItem] oi
            -- Delete the Order Items that no longer exist at the source
            LEFT JOIN @LiveRows lr ON oi.[OrderId] = lr.[OrderId]
        WHERE lr.OrderId IS NULL
    ;

    SET @DeleteCount = @DeleteCount + @@ROWCOUNT;

    -- Delete parent Order rows that no longer have child Order Item rows

    DELETE FROM [Order]
        FROM [Order] o
            -- Delete the Orders that no longer exist at the source
            LEFT JOIN @LiveRows lr ON o.[OrderId] = lr.[OrderId]
        WHERE lr.OrderId IS NULL
    ;

    SET @DeleteCount = @DeleteCount + @@ROWCOUNT;

    SELECT MergeAction, COUNT(*) AS ActionCount FROM @LiveRows GROUP BY MergeAction
    UNION
    SELECT 'DELETE' AS MergeAction, @DeleteCount AS ActionCount
    ;
END

一切都在一个甜蜜的循环-dee-loop 流式往返行程中完成,并对关键索引进行了高度优化。即使内部主键值从源中未知,MERGE 操作也使它们可用于 DELETE 操作。

Customer MERGE 使用不同的@LiveRows TABLE 结构,因此使用不同的 OUTPUT 语句和不同的 DELETE 语句...

CREATE PROCEDURE MyCustomerMerge @SourceValues [MyCustomerSqlUserType] READONLY
AS
BEGIN
    DECLARE @LiveRows TABLE (MergeAction VARCHAR(20), CustomerId INT);
    DECLARE @DeleteCount INT;

    SET @DeleteCount = 0;

    MERGE INTO [Customer] AS [target]

        ...

        OUTPUT $action, INSERTED.[CustomerId] INTO @LiveRows

    ;   -- MERGE has ended

    -- Delete child OrderItem rows before parent Order rows

    DELETE FROM [OrderItem]
        FROM [OrderItem] oi
            JOIN [Order] o ON oi.[OrderId] = o.[OrderId]
                -- Delete the Order Items that no longer exist at the source
                LEFT JOIN @LiveRows lr ON o.[CustomerId] = lr.[CustomerId]
        WHERE lr.CustomerId IS NULL
    ;

    SET @DeleteCount = @DeleteCount + @@ROWCOUNT;

    -- Delete child Order rows before parent Customer rows

    DELETE FROM [Order]
        FROM [Order] o
            -- Delete the Orders that no longer exist at the source
            LEFT JOIN @LiveRows lr ON o.[CustomerId] = lr.[CustomerId]
        WHERE lr.CustomerId IS NULL
    ;

    SET @DeleteCount = @DeleteCount + @@ROWCOUNT;

    -- Delete parent Customer rows that no longer have child Order or grandchild Order Item rows

    DELETE FROM [Customer]
        FROM [Customer] c
            -- Delete the Customers that no longer exist at the source
            LEFT JOIN @LiveRows lr ON c.[CustomerId] = lr.[CustomerId]
        WHERE lr.CustomerId IS NULL
    ;

    SET @DeleteCount = @DeleteCount + @@ROWCOUNT;

    SELECT MergeAction, COUNT(*) AS ActionCount FROM @LiveRows GROUP BY MergeAction
    UNION
    SELECT 'DELETE' AS MergeAction, @DeleteCount AS ActionCount
    ;
END

设置和维护有点麻烦 - 但值得收获的效率。

【讨论】:

    【解决方案2】:

    您可以使用输出子句将修改/插入的行捕获到表变量中,并在合并后将其与删除语句一起使用。

    DECLARE @T TABLE(EmployeeID INT);
    
    MERGE Target1 AS T
    USING Source1 AS S
    ON (T.EmployeeID = S.EmployeeID) 
    WHEN NOT MATCHED BY TARGET AND S.EmployeeName LIKE 'S%' 
        THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
    WHEN MATCHED 
        THEN UPDATE SET T.EmployeeName = S.EmployeeName
    WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
        THEN DELETE  
    OUTPUT S.EmployeeID INTO @T;
    
    DELETE Source1
    WHERE EmployeeID in (SELECT EmployeeID
                         FROM @T);
    

    【讨论】:

      【解决方案3】:

      你也可以使用下面的代码

      drop table energydata
      
      create table temp_energydata  
      (
      webmeterID int,
      DT DateTime ,
      kWh varchar(10)
      )
      
      Insert into temp_energydata
      select 1,getdate()-10, 120
      union
      select 2,getdate()-9, 140
      union
      select 3,getdate()-6, 37
      union
      select 4,getdate()-3, 40
      union
      select 5,getdate()-1, 240
      
      create table  energydata
      (
      webmeterID int,
      DT DateTime ,
      kWh varchar(10)
      ) 
      
      Insert into energydata (webmeterID,kWh)
      select 1, 120
      union
      select 2, 140
      union
      select 3, 37
      union
      select 4, 40
      
      select * from energydata 
      select * from temp_energydata
      
      begin tran ABC
      
      DECLARE @T TABLE(ID INT);
      
      MERGE INTO dbo.energydata WITH (HOLDLOCK) AS target
      USING dbo.temp_energydata AS source
          ON target.webmeterID = source.webmeterID
          AND target.kWh  = source.kWh 
      
      WHEN MATCHED THEN 
          UPDATE SET target.DT = source.DT
      
      WHEN NOT MATCHED BY source THEN  delete   
       OUTPUT source.webmeterID INTO @T;
      
      
      DELETE temp_energydata
      WHERE webmeterID in (SELECT webmeterID
                           FROM @T);
          --INSERT (webmeterID, DT, kWh)
          --VALUES (source.webmeterID, source.DT, source.kWh)
      
      
      rollback  tran ABC
      commit  tran ABC
      

      【讨论】:

        【解决方案4】:


        很好的响应,但是您的代码将从目标表中删除该行,这是一个示例,您可以在不影响目标表的情况下从源目标中删除行:

        if OBJECT_ID('audit.tmp1') IS NOT NULL
            DROP TABLE audit.tmp1   
        
        select *
        into audit.tmp1
        from 
        (
        select 1 id, 'aa' nom, convert(date,'2014-01-01') as dd UNION ALL 
        select 2 id, 'bb' nom, convert(date,'2013-07-12') as dd UNION ALL 
        select 3 id, 'cc' nom, convert(date,'2012-08-21') as dd UNION ALL 
        select 4 id, 'dd' nom, convert(date,'2011-11-15') as dd UNION ALL 
        select 5 id, 'ee' nom, convert(date,'2010-05-16') as dd ) T
        
        
        if OBJECT_ID('audit.tmp2') IS NOT NULL
        DROP TABLE audit.tmp2   
        
        select *
        into audit.tmp2
        from 
        (
        select 1 id, 'aAa' nom, convert(date,'2014-01-14') as dd UNION ALL 
        select 2 id, 'bbB' nom, convert(date,'2013-06-13') as dd UNION ALL 
        select 4 id, 'dDD' nom, convert(date,'2012-11-05') as dd UNION ALL 
        select 6 id, 'FFf' nom, convert(date,'2014-01-12') as dd) T
        
        
        SELECT * FROM audit.tmp1 order by 1
        SELECT * FROM audit.tmp2 order by 1
        
        
        DECLARE @T TABLE(ID INT);
        
        MERGE audit.tmp2 WITH (HOLDLOCK) AS T
        USING (SELECT * FROM audit.tmp1 WHERE nom <> 'dd') AS S
        ON (T.id = S.id)
        WHEN NOT MATCHED BY TARGET
        THEN INSERT(id, nom, dd) VALUES(S.id, S.nom, S.dd)
        WHEN MATCHED 
        THEN UPDATE SET T.nom = S.nom, T.dd = S.dd
        WHEN NOT MATCHED BY SOURCE
        THEN UPDATE SET T.id = T.id OUTPUT S.id INTO @T;
        
        DELETE tmp1
        FROM audit.tmp1
        INNER JOIN
        @T AS DEL
            ON DEL.id = tmp1 .id
        
        
        SELECT * FROM audit.tmp1 ORDER BY 1
        SELECT * FROM audit.tmp2 ORDER BY 1
        

        希望对你有帮助。

        【讨论】:

          猜你喜欢
          • 2014-08-09
          • 1970-01-01
          • 2011-04-04
          • 2014-07-19
          • 1970-01-01
          • 2011-08-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多