web-dev-qa-db-fra.com

Comment supprimer des sources à l'aide de la commande MERGE dans SQL Server 2008?

J'utilise actuellement le code de fusion ci-dessous pour migrer la date de la source à la cible. J'ai une nouvelle exigence d'étendre le code ci-dessous pour supprimer l'enregistrement de la source une fois qu'une mise à jour/insertion est effectuée sur la cible. Est-ce possible en utilisant la fusion (tous les exemples que je vois sur le net ont exécuté del/insert/update dans la cible pas dans la source)

    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  ;
22
nfa379

Vous pouvez utiliser la clause de sortie pour capturer les lignes modifiées/insérées dans une variable de table et l'utiliser avec une instruction delete après la fusion.

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);
33
Mikael Eriksson


Bonne réponse, mais votre code supprimera la ligne de votre table de destination, voici un exemple dans lequel vous pouvez supprimer les lignes de votre destination source sans affecter votre table cible:

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

J'espère que cela t'aidera.

2
Youssef DAOUI

Dans notre cas, nous voulions utiliser MERGE pour synchroniser notre base de données interne avec une source externe d'une structure différente. Les réglages automatisés de CASCADE n'étaient pas une option, car nous entretenons de nombreuses relations cycliques et, en réalité, nous n'aimons pas ce type de pouvoir bon marché entre les mains de membres du personnel mécontents. Nous ne pouvons pas supprimer les lignes parent avant que leurs lignes enfants aient disparu.

Tout cela est fait avec des MERGE ultrarapides qui utilisent les paramètres de valeur de table. Ils offrent, de loin, les meilleures performances avec une surcharge de mémoire de manière obscène.

Combinaison d'avis dispersés pour les données 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

Tout est fait en un aller-retour fluide et optimisé sur des index clés. Même si les valeurs de clé primaire internes sont inconnues de la source, l'opération MERGE les met à la disposition des opérations DELETE.

Le client MERGE utilise une structure @LiveRows TABLE différente, donc une instruction OUTPUT différente et des instructions DELETE différentes ...

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

L’installation et la maintenance sont un peu pénibles - mais les gains d’efficacité obtenus en valent vraiment la peine.

0
KramFfud

vous pouvez aussi utiliser le code ci-dessous

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
0
hardik rawal