T-SQL Ninja #15

MERGE

Was ist MERGE?

Über Merge-Statements könnte man beinahe ein Buch schreiben. Doch als echte Ninjas möchten wir das Thema mit dem Katana fein säuberlich zerteilen. Ein MERGE Statement führt in einem Statement Einfüge-, Lösch- oder Update-Operationen in einer Tabelle aus. Es kann beispielsweise dafür verwendet werden, Tabellen zu synchronisieren, also die Inhalte einer Tabelle falls noch nicht vorhanden in eine zweite Tabelle einzufügen, zu aktualisieren, falls sie schon vorhanden sind und aus der zweiten Tabelle zu löschen, wenn sie in der ersten Tabelle nicht vorhanden sind.

Was ihr beim MERGE-Statement beachten solltet ist, dass es zum Einen eine relativ komplexe Syntax verwendet und dass es um Anderen nicht so einfach ist, derartige Statements zu tunen, wenn ihr eine Wahl habt, sind daher oftmals getrennte INSERTUPDATE und DELETE-Statements die bessere Wahl.

Wie könnt ihr mit MERGE arbeiten?

Eigentlich ist die Verwendung von MERGE Statements nicht so besonders kompliziert, allerdings kennen wir keinen Ninja, der sie aus dem Kopf reproduziert bekommt. Beginnen wir zunächst damit, uns Daten zu erzeugen, aus denen wir die Datenbank updaten möchten. Das machen wir, indem wir eine Kopie der StockItem-Tabelle erzeugen:

SELECT [StockItemID]
      ,[StockItemName]
      ,[SupplierID]
      ,[ColorID]
      ,[UnitPackageID]
      ,[OuterPackageID]
      ,[Brand]
      ,[Size]
      ,[LeadTimeDays]
      ,[QuantityPerOuter]
      ,[IsChillerStock]
      ,[Barcode]
      ,[TaxRate]
      ,[UnitPrice] = [UnitPrice]+2.0
      ,[RecommendedRetailPrice] = [RecommendedRetailPrice]+2.0
      ,[TypicalWeightPerUnit]
      ,[MarketingComments]
      ,[InternalComments]
      ,[Photo]
      ,[CustomFields]
      ,[LastEditedBy]
  INTO [Warehouse].[StockItemUpdate] 
  FROM [Warehouse].[StockItems]
  WHERE StockItemID > 5

Wir haben dabei die Preise angepasst und filtern die Items mit den niedrigsten StockItemIDs heraus. Wir haben nun also eine Tabelle mit angepassten Item-Preisen, die nicht alle Items enthält. Da auf der StockItems-Tabelle Fremdschlüsselbeziehungen liegen, die uns im vorliegenden Fall das Leben schwer machen würden, legen wir von der Tabelle auch nochmal eine Kopie an:

SELECT [StockItemID]
      ,[StockItemName]
      ,[SupplierID]
      ,[ColorID]
      ,[UnitPackageID]
      ,[OuterPackageID]
      ,[Brand]
      ,[Size]
      ,[LeadTimeDays]
      ,[QuantityPerOuter]
      ,[IsChillerStock]
      ,[Barcode]
      ,[TaxRate]
      ,[UnitPrice]
      ,[RecommendedRetailPrice]
      ,[TypicalWeightPerUnit]
      ,[MarketingComments]
      ,[InternalComments]
      ,[Photo]
      ,[CustomFields]
      ,[LastEditedBy]
  INTO [Warehouse].[StockItemCopy] 
  FROM [Warehouse].[StockItems]

Nun möchten wir die Update-Tabelle mit der Kopie der StockItem-Tabelle synchronisieren. Dafür verwenden wir das MERGE Statement. Grundsätzlich ist das wie folgt aufgebaut: Es beginnt mit einem MERGE-Befehl, darauf folgt die Definition des Ziels und der Quelle sowie eine ON-Bedingung, wie die passenden Zeilen in Quelle und Ziel gefunden werden. Danach folgen ein oder mehrere Blöcke, die definieren, was passiert, wenn Quelle und Ziel MATCHen oder eben nicht (wobei diese Blöcke auch zusätzliche Bedingungen enthalten können). Konkret gehen wir wie folgt vor: wenn wir passende Zeilen finden, dann aktualisieren wir die Preise (wir können hier natürlich auch alle anderen Spalten aktualisieren), wenn die Zeile in der Quelle (also der Update-Tabelle) vorhanden ist, aber nicht im Ziel, dann fügen wir sie ein, wenn sie im Ziel vorhanden ist, aber nicht in der Quelle, dann löschen wir sie aus dem Ziel. In der Update-Tabelle waren 222 Zeilen enthalten von 227, die in der StockItems-Tabelle liegen (wir hatten schließlich auf die StockItemID gefiltert. Führen wir nun folgendes MERGE-Statement aus:

MERGE [Warehouse].[StockitemCopy] AS tgt  
    USING (SELECT [StockItemID]
      ,[StockItemName]
      ,[SupplierID]
      ,[ColorID]
      ,[UnitPackageID]
      ,[OuterPackageID]
      ,[Brand]
      ,[Size]
      ,[LeadTimeDays]
      ,[QuantityPerOuter]
      ,[IsChillerStock]
      ,[Barcode]
      ,[TaxRate]
      ,[UnitPrice]
      ,[RecommendedRetailPrice]
      ,[TypicalWeightPerUnit]
      ,[MarketingComments]
      ,[InternalComments]
      ,[Photo]
      ,[CustomFields]
      ,[LastEditedBy] from [Warehouse].[StockItemUpdate]) AS src
    ON (tgt.[StockItemID] = src.[StockItemID])  
    WHEN MATCHED THEN
        UPDATE SET 
             [UnitPrice] = src.[UnitPrice]
            ,[RecommendedRetailPRice] = src.[RecommendedRetailPRice]
    WHEN NOT MATCHED BY TARGET THEN 
        INSERT ([StockItemID]
      ,[StockItemName]
      ,[SupplierID]
      ,[ColorID]
      ,[UnitPackageID]
      ,[OuterPackageID]
      ,[Brand]
      ,[Size]
      ,[LeadTimeDays]
      ,[QuantityPerOuter]
      ,[IsChillerStock]
      ,[Barcode]
      ,[TaxRate]
      ,[UnitPrice]
      ,[RecommendedRetailPrice]
      ,[TypicalWeightPerUnit]
      ,[MarketingComments]
      ,[InternalComments]
      ,[Photo]
      ,[CustomFields]
      ,[LastEditedBy])
      VALUES 
      (src.[StockItemID]
      ,src.[StockItemName]
      ,src.[SupplierID]
      ,src.[ColorID]
      ,src.[UnitPackageID]
      ,src.[OuterPackageID]
      ,src.[Brand]
      ,src.[Size]
      ,src.[LeadTimeDays]
      ,src.[QuantityPerOuter]
      ,src.[IsChillerStock]
      ,src.[Barcode]
      ,src.[TaxRate]
      ,src.[UnitPrice]
      ,src.[RecommendedRetailPrice]
      ,src.[TypicalWeightPerUnit]
      ,src.[MarketingComments]
      ,src.[InternalComments]
      ,src.[Photo]
      ,src.[CustomFields]
      ,src.[LastEditedBy]) 
    WHEN NOT MATCHED BY SOURCE THEN
        DELETE;

Wenn wir nun die Kopie der StockItems-Tabelle prüfen, enthält sie genau 222 Zeilen, bei denen die Preise allesamt 2 Euro höher sind als in der originalen StockItems-Tabelle.

Referenzen:

Ninja-Notebooks @ GitHub

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.