MERGE

MERGE ist ein mächtiges und komplexes Statement zum Einpflegen von Bewegungsdaten in eine Stammtabelle. Während früher die DV-Azubis Standardalgorithmen zum "Mischen von Bewegungsdaten in Stammdaten" noch auswendig lernen mussten und ganze Testprogramme dazu schrieben, kann dies SQL heute größtenteils durch ein einziges Statement abbilden. Es ist UPDATE, DELETE und INSERT in einem. Die Daten werden anhand von Schlüsselwerten miteinander in Verbindung gebracht und die Zieltabelle basierend auf den Daten und Beziehungen verändert.

globale Syntax

MERGE INTO Ziel
USING Source
ON Verknüpfung
WHEN Bedingung(en) THEN Aktion
WHEN andere Bedingung(en) THEN andere Aktion
weitere Optionen



Ziel

Das Ziel der Operation, also das Objekt, bei dem die Daten verändert werden, ist -wenig verwunderlich für ein DB2-System- eine Tabelle. Es kann aber auch eine VIEW angegeben werden, ein ALIAS oder auch eine DECLARED GLOBAL TEMPORARY TABLE. Nicht erlaubt ist dagegen eine CREATED GLOBAL TEMPORARY TABLE oder Objekte, die grundsätzlich nicht updateable sind (wie DIRECTORY, CATALOG, systemgepflegte MQTs und read-only views). Wird eine VIEW angegeben, dürfen keine INSTEAD OF-trigger darauf definiert sein.
Wie von den meisten Statements gewohnt kann hinter dem Tabellenname mit einem "AS xxx" ein Correlationsname angegeben werden.
Beispiel:

MERGE INTO PROD.WARENBESTAND AS WB

Source

Hier werden die Daten angegeben, die in das Ziel eingepflegt werden sollen. Die Daten können auf vielfältige Weise vorgegeben werden, DB2 muss sie nur als Tabelle interpretieren können. Im einfachsten Fall ist die Source ebenfalls das Ergebnis ein SELECTs, es ist aber auch möglich, die Werte mittels VALUES() oder TABLE() zu übergeben. Wie zuvor könnem mit "AS yyy ( col1 ,col2 ...)" Korrelationsnamen für die Tabelle und die Spalten angegeben werden.
Beispiel1:

USING ( SELECT ID, MENGE FROM PROD.LIEFERUNG WHERE LIEFERUNGSDATUM = CURRENT DATE - 1 DAY ) AS WV


Beispiel2:
es existieren Hostvariablen:

DCL ID(10) BIN FIXED(31);
DCL MENGE(10) DEC FIXED(15,2);
USING ( VALUES(:ID , :MENGE) FOR 10 ROWS ) AS SRC ( ID , MENGE )


ON Verknüpfung

Hier wird die Beziehung des Sources zum Ziel definiert, also ob die Daten der Source zu einer (oder mehreren) Zeilen der Zieltabelle passen. Die Syntax entspricht dabei der eines OUTER JOINS. Tatsächlich wird intern ein
Source LEFT OUTER JOIN ziel ON Verknüpfung
durchgeführt um die Zeilen der Source dem Ziel zuordnen zu können.
Beispiel:

ON (WB.ID = WV.ID)


WHEN ... THEN

Hier wird angegeben, welche Bedingungen erfüllt sein müssen, damit die nachfolgende Aktion ausgeführt wird. Die erste Bedingung muss dabei immer WHEN MATCHED (es gibt in der Source-Tabelle eine Zeile, deren Schlüssel gemäß ON-Klausel zu der entsprechenden Zeile der Ziel-Tabelle passt) oder WHEN NOT MATCHED (wenn es keine gibt) sein. Weitere Bedingungen können mit AND danach folgen. Werden mehrere WHEN Bedingungen nacheinander kodiert und mehrere unterschiedliche sind zutreffend, dann wird nur die Aktion nach der ersten passenden Bedingung ausgeführt.
Beispiel1:

WHEN NOT MATCHED THEN


Beispiel2:

WHEN MATCHED AND (WV.AKTION = 'ADD') THEN


Aktion

jetzt passiert endlich mal was. Aktion kann ein DELETE, INSERT, UPDATE oder ein SIGNAL sein.

DELETE

Ein DELETE löscht die gematchte Zeile aus der Zieltabelle. DELETE kann nur in Verbindung mit WHEN MATCHED und nicht zusammen mit NOT ATOMIC CONTINUE ON SQLEXCEPTION verwendet werden.
Beispiel:

WHEN MATCHED THEN DELETE


INSERT

Ein INSERT fügt eine einzelne Zeile in die Zieltabelle ein. Es ist nur ein INSERT VALUES (...) möglich, kein INSERT einer kompletten Ergebnismenge, also nicht die Variante INSERT ... SELECT FROM. Der INSERT braucht keine INTO-Klausel, die Zieltabelle ist ja bereits benannt. Einzelne Spaltennamen können aber nach dem INSERT angegeben werden. INSERT ist nur in Verbindung mit NOT MATCHED möglich.
Beispiel:

WHEN NOT MATCHED THEN INSERT (ID,WERT) VALUES(WV.ID,WV.MENGE)


UPDATE

Ein UPDATE selbst besteht nur aus der SET-Klausel. Der zu setzende Wert kann dabei ein Spalteninhalt sein, eine Hostvariable, NULL oder DEFAULT. Es können auch mehrere Werte durch einen SELECT zugewiesen werden, sofern die Resulttabelle nur aus einer einzelnen Zeile besteht. Würde ein entsprechender SELECT keine Ergebniszeile zurückliefern, dann würden alle Werte des Updates auf <NULL> gesetzt. Wäre eine der Spalten nicht NULL-fähig, ergäbe es einen Fehler. UPDATE ist nur bei einem WHEN MATCHED möglich. Bei Verwendung der Option "NOT ATOMIC CONTINUE ON SQLEXCEPTION" ist nur WHEN MATCHED, aber keine weitere Bedingung (AND ...) erlaubt
Beispiel:

WHEN MATCHED AND(WV.AKTION='ADD') THEN UPDATE SET WB.BESTAND = WB.BESTAND + WV.MENGE


SIGNAL

Ein SIGNAL erzeugt einen Fehler mit dem angegebenen SQLSTATE. Kann nicht zusammen mit "NOT ATOMIC CONTINUE ON SQLEXCEPTION" verwendet werden
Beispiel:

WHEN MATCHED AND(WV.AKTION='SUB' AND WB.BESTAND < WV.MENGE) THEN SIGNAL SQLSTATE '75001'




weitere Optionen

ELSE IGNORE

trifft keine der obigen Bedingungen zu, dann soll die entsprechende Zeile der Source-Tabelle ignoriert werden. Werden dadurch alle Zeilen ignoriert, erfolgt eine Warnung.

NOT ATOMIC CONTINUE ON SQLEXCEPTION

Diese Option besagt, dass jede Zeile der Source-Tabelle separat behandelt wird (in etwa so, als ob jede Zeile ihr eigenes MERGE-Statement hätte und dadurch entsprechend viele MERGE-Statements nacheinander abgearbeitet würden). Das MERGE-Statement kann dadurch insgesamt erfolgreich sein, selbst wenn einzelne Zeilen einen Fehler ergeben. TRIGGER auf der Zieltabelle werden jedoch nur für die Zeilen ausgelöst, für die die Operation erfolgreich war. Die Option darf nicht verwendet werden, wenn in der USING-Klausel eine Tabelle referenziert wird, sondern nur wenn dort die VALUES-Klausel Anwendung findet.

QUERYNO integer

die angegebene Querynummer wird für den Output eines EXPLAINs verwendet, ebenso für Traces. Die Nummer gilt auch für die Systemtabellen SYSIBM.SYSSTMT und SYSIBM.SYSPACKSTMT .

weitere Details

Wie bereits oben erwähnt, wird das Statement je nach Angabe der Option "NOT ATOMIC CONTINUE ON SQLEXCEPTION" entweder ALL-IN-ONE abgearbeitet, oder aber Zeile für Zeile separat. Ist das erste der Fall (die Klausel ist nicht angegeben), gilt:
Jede Zeile der Zieltabelle kann insgesamt maximal einmal angesprochen werden. (Die in der ON-Klausel angegebene Spalte oder Spaltenkombination muss also für die Source-Tabelle UNIQUE sein). Andererseits kann ein Zeile der Source-Tabelle beliebig viele Zeilen in der Ziel-Tabelle matchen.
Ein TRIGGER auf der Zieltabelle oder eine definierte RI darf diese nicht selbst zum Ziel haben.
Eine Zeile, die durch das MERGE-Statement eingefügt wurde, kann durch das gleiche Statement nicht UPDATEs werden.

In letzterem Fall (die Klausel ist angegeben), oder die SOURCE-Tabelle wird über die VALUES(...) erstellt gilt:
Das MERGE-Statement darf nur eine UPDATE-Klausel und nur eine INSERT-Klausel beinhalten. Außer MATCHED und NOT MATCHED ist keine weitere Bedingung erlaubt
Eine einzelne Zeile der Ziel-Tabelle kann öfters als einmal durch einen UPDATE verändert werden.
Die Inhhalte von special register, sequences und functions werden für jede Zeile neu ermittelt.
FOR EACH STATEMENT - Trigger, (die eigentlich nur einmal pro statement triggern), triggern in diesem Fall für jede einzelne verarbeitete Zeile.
Außer MATCHED und NOT MATCHED dürfen keine weiteren Bedingungen vorgegeben werden.
Es ist kein DELETE erlaubt, kein SIGNAL, kein ELSE IGNORE und kein SELECT in der UPDATE-Klausel

Die Gesamtanzahl der betroffenen Zeilen ist in der Spalte SQLERRD(3) der SQLCA hinterlegt.

SELECT FROM FINAL TABLE

MERGE erzeugt eine Reihe von veränderten Zeilen. Diese können wiederum als Ursprung eines SELECTs dienen. Ja, das gilt auch für dieses Statement.
Ein SELECT * FROM FINAL TABLE ( MERGE ... ); erzeugt eine Ergebnismenge, die alle modifizierten Zeilen beinhaltet. Und nicht nur das. Das MERGE-Statement selbst erlaubt es, diese Ergebnismenge noch zu erweitern. Eine INCLUDE-Klausel (die ich bisher unterschlagen habe) erlaubt es, weitere Spalten in die Result-Table aufzunehmen. Die Klausel befindet sich zwischen der MERGE INTO und der USING Klausel. Die Klausel tut so, als wäre die Spalte Bestandteil der Zieltabelle. Sie kann daher beim UPDATE mit SET= und beim INSERT mit der VALUES-Klausel befüllt werden. Die Spalte(n) haben jedoch nur Auswirkungen auf die FINAL TABLE. Das INCLUDE ist daher auch nur erlaubt, wenn das MERGE-Statement in der FROM FINAL TABLE-Klausel eines SELECTs auftaucht. Wie das Ganze funktioniert ist am Ende des Beiträgs näher erläutert.

Anmerkungen

Das GET DIAGNOSTICS-Statement kann (und sollte) verwendet werden, um zu prüfen, ob und warum einzelne Zeilen des MERGE-Statements auf einen Fehler gelaufen sind.
Wird bei einem INSERT keine Spaltenliste mitgegeben, werden HIDDEN columns nicht berücksichtigt. Diese Spalten sollten also einen Default haben.
Bei einem UPDATE sollte minimalistisch vorgegangen werden. Nur die Spalten angeben, die tatsächlich geändert werden. Tauchen Spalten eines Index in einer UPDATE-Klausel auf, wird dieser Index für das Statement nicht verwendet.

Beispiele

Soweit zur Theorie. Uns wie sieht das ganze jetzt in der Praxis aus? Hier sind ein paar Beispiele.

Gehen wir von folgenden Beispieltabellen aus: Eine Warenbestandstabelle und eine Liefertabelle. Die Lieferungen sollen in die Bestandstabelle eingepflegt werden, der entsprechende Bestand soll erhöht werden.

WARENBESTAND
IDBeschreibungMenge
101Schraube1000
102Nagel4000
201Hammer5
202Zange2
LIEFERUNG
IDMenge
102100
2011
5011

MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID, MENGE FROM LIEFERUNG ) LF
ON (WB.ID = LF.ID)
WHEN MATCHED THEN UPDATE SET WB.MENGE = WB.MENGE + LF.MENGE
ELSE IGNORE


Intern werden nun die beiden Tabellen analog eines OUTER JOIN verknüpft:

interne Tabelle
LF.IDLF.MENGEWB.IDWB.BESCHREIBUNGWB.MENGE
102100102Nagel4000
2011201Hammer5
5011NOT MATCHED

Nun werden die Zeilen, die MATCHen, in der Tabelle WARENBESTAND gemäß der WHEN MATCHED Anweisung geändert. Die übriggebliebene Zeile wird ignoriert, weil es keine WHEN NOT MATCHED Anweisung gibt und das ELSE INGORE gültig ist. Der WARENBESTAND sieht nach der Abarbeitung des Statements so aus:

WARENBESTAND
IDBeschreibungMenge
101Schraube1000
102Nagel4100
201Hammer6
202Zange2

Allerdings würde dieses Statement erfolgreich ausgeführt. Dass es eine offensichtliche Inkonsistenz in der LIEFER-Tabelle gibt, nämlich eine ID, zu der es keine Bestandsdaten gibt, würde nicht erkannt. Eine kleine Änderung des MERGE-Statements könnte hier Abhilfe schaffen:

MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID, MENGE FROM LIEFERUNG ) LF
ON (WB.ID = LF.ID)
WHEN MATCHED THEN UPDATE SET WB.MENGE = WB.MENGE + LF.MENGE
WHEN NOT MATCHED THEN SIGNAL SQLSTATE '75001' SET MESSAGE_TEXT = CHAR(LF.ID) CONCAT ' IST IM BESTAND NICHT VORHANDEN'
ELSE IGNORE


In diesem Fall würde der MERGE nicht erfolgreich sein, sondern abbrechen mit SQLCODE = -438. APPLICATION RAISED ERROR WITH DIAGNOSTIC TEXT: 501 IST IM BESTAND NICHT VORHANDEN
Das ELSE IGNORE wegzulassen würde nicht helfen, da es Default ist.
Ebenfalls wäre es möglich, stattdessen eine entsprechende Zeile in die BESTAND-Tabelle aufzunehmen. Problem hier ist jedoch, dass dann keine Beschreibung des neuen Objektes vorhanden ist.

MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID, MENGE FROM LIEFERUNG ) LF
ON (WB.ID = LF.ID)
WHEN MATCHED THEN UPDATE SET WB.MENGE = WB.MENGE + LF.MENGE
WHEN NOT MATCHED THEN INSERT VALUES(LF.ID,'???',LF.MENGE)
ELSE IGNORE


wenn die Spalte BESCHREIBUNG in der WARENBESTAND-Tabelle nullfähig ist, dann könnte die Zeile auch so lauten:

WHEN NOT MATCHED THEN INSERT (ID,MENGE) VALUES(LF.ID,LF.MENGE)



Die neue BESTAND-Tabelle würde danach so aussehen:

WARENBESTAND
IDBeschreibungMenge
101Schraube1000
102Nagel4100
201Hammer6
202Zange2
501???1


Nehmen wir nun statt einer LIEFER-Datei eine BEWEGUNG-Datei, bei der es Zu- und Abgänge gibt. Die sieht so aus:

BEWEGUNG
IDMengeArt
10150KAUF
10230VERKAUF
10170VERKAUF
2011KAUF
102100KAUF

Jetzt könnte man meinen, dieses Statement wäre passend, um die beiden Tabellen zusammen zu mischen:

MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID, MENGE,ART FROM BEWEGUNG ) BE
ON (WB.ID = BE.ID)
WHEN MATCHED AND(BE.ART = 'KAUF') THEN UPDATE SET WB.MENGE = WB.MENGE + BE.MENGE
WHEN MATCHED AND(BE.ART = 'VERKAUF') THEN UPDATE SET WB.MENGE = WB.MENGE - BE.MENGE
WHEN NOT MATCHED AND(BE.ART = 'KAUF') THEN INSERT VALUES(BE.ID,'???',BE.MENGE)
WHEN NOT MATCHED AND(BE.ART = 'VERKAUF') THEN SIGNAL SQLSTATE('75001')
ELSE IGNORE


Rein syntaktisch würde das Ganze sogar funktionieren. Problematisch ist hier allerdings, dass mehrere Zeilen der BEWEGUNG die gleiche WARENBESTAND-Zeile ändern würden. Und das ist nicht erlaubt (SQLCODE = -788). Die Option NOT ATOMIC CONTINUE ON SQLEXCEPTION ist auch nicht verwendbar, weil in der USING-Klausel eine Tabelle referenziert wird (SQLCODE=-104) und sie würde zudem auch nur eine einzige UPDATE und eine einzige INSERT Klausel erlauben (sonst SQLCODE=-637). In diesem Fall muss der SELECT in der USING-Klausel so umformuliert werden, dass jede ID nur noch einmal vorkommt.

MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID, SUM(CASE WHEN ART='KAUF' THEN MENGE WHEN ART='VERKAUF' THEN -1*MENGE END) FROM BEWEGUNG GROUP BY ID ) AS BE(ID,MENGE)
ON (WB.ID = BE.ID)
WHEN MATCHED THEN UPDATE SET WB.MENGE = WB.MENGE + BE.MENGE
WHEN NOT MATCHED THEN SIGNAL SQLSTATE('75001')
ELSE IGNORE


Für die nächsten Beispiele erweitern wir die ursprüngliche BESTAND-Tabelle und unterscheiden zwischen Sollbestand und Istbestand. Zusätzlich hat der Vorstand für das kommende Jahr neue Direktiven bschlossen, was den Bestand angeht:

WARENBESTAND
IDBeschreibungIstmengeSollmenge
101Schraube10001000
102Nagel40003000
201Hamer55
202Zange25
DIREKTIVE
IDSollmengeBeschreibungKommentar
101<NULL><NULL>alles so lassen
1025000<NULL>Sollmenge ändern
201<NULL>HammerName korrigieren
2020<NULL>durch Nachfolgemodell ersetzen
2035Zange MK2neu aufnehmen


Und nun soll die Direktive mit einem einzigen Statement in der Bestandstabelle abgebildet werden:

MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID,SOLLMENGE,BESCHREIBUNG FROM DIREKTIVE WHERE SOLLMENGE IS NOT NULL OR BESCHREIBUNG IS NOT NULL ) AS DIR
ON ( WB.ID = DIR.ID )
WHEN MATCHED AND(DIR.SOLLMENGE = 0) THEN DELETE
WHEN MATCHED THEN UPDATE SET WB.SOLLMENGE = COALESCE(DIR.SOLLMENGE,WB.SOLLMENGE), WB.BESCHREIBUNG=COALESCE(DIR.BESCHREIBUNG,WB.BESCHREIBUNG)
WHEN NOT MATCHED THEN INSERT VALUES(DIR.ID,DIR.BESCHREIBUNG,0,DIR.SOLLMENGE)
ELSE IGNORE


ein Aufteilen der WHEN MATCHED Bedingung auf:

WHEN MATCHED AND (WB.SOLLMENGE<>DIR.SOLLMENGE) THEN UPDATE SET WB.SOLLMENGE = DIR.SOLLMENGE
WHEN MATCHED AND (WB.BESCHREIBUNG<>DIR.BESCHREIBUNG) THEN UPDATE SET WB.BESCHREIBUNG = DIR.BESCHREIBUNG

würde nicht funktionieren, wenn sowohl die Sollmenge als auch die Beschreibung geändert werden soll. Nur die erste passende Klausel wird berücksichtigt, es würde also nur die Menge, nicht die Beschreibung geändert.
Das Ergebnis ist (hoffentlich) ein SQLCODE=0, aber außer der zusätzlichen Information über die Anzahl der betroffenen Sätze in der SQLCA.SQLERRD(3) bekomme ich keine weitere Information geliefert. Doch wozu gibt es denn den SELECT FROM FINAL TABLE ? Also frohgemut das Statement kurz erweitert um:

SELECT * FROM FINAL TABLE (
MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID,SOLLMENGE,BESCHREIBUNG FROM DIREKTIVE WHERE SOLLMENGE IS NOT NULL OR BESCHREIBUNG IS NOT NULL ) AS DIR
ON ( WB.ID = DIR.ID )
WHEN MATCHED AND(DIR.SOLLMENGE = 0) THEN DELETE
WHEN MATCHED THEN UPDATE SET WB.SOLLMENGE = COALESCE(DIR.SOLLMENGE,WB.SOLLMENGE), WB.BESCHREIBUNG=COALESCE(DIR.BESCHREIBUNG,WB.BESCHREIBUNG)
WHEN NOT MATCHED THEN INSERT VALUES(DIR.ID,DIR.BESCHREIBUNG,0,DIR.SOLLMENGE)
ELSE IGNORE
)

und ... schon bricht das Ganze mit SQLCODE=-270 ab. Die DELETE-Klausel spielt hier den bösen Buben. Eigentlich verständlich, wie kann der SELECT auch Zeilen lesen, die nicht mehr da sind. Um die Funktionsweise des SELECT FROM FINAL TABLE zu erläutern, entfernen wir einfach die DELETE Klausel (ist ja nur ein Beispiel). Diesmal bekommen wir ein Ergebnis.

SELECT * FROM FINAL TABLE (
MERGE INTO WARENBESTAND AS WB
USING ( SELECT ID,SOLLMENGE,BESCHREIBUNG FROM DIREKTIVE WHERE SOLLMENGE IS NOT NULL OR BESCHREIBUNG IS NOT NULL ) AS DIR
ON ( WB.ID = DIR.ID )
WHEN MATCHED THEN UPDATE SET WB.SOLLMENGE = COALESCE(DIR.SOLLMENGE,WB.SOLLMENGE), WB.BESCHREIBUNG=COALESCE(DIR.BESCHREIBUNG,WB.BESCHREIBUNG)
WHEN NOT MATCHED THEN INSERT VALUES(DIR.ID,DIR.BESCHREIBUNG,0,DIR.SOLLMENGE)
ELSE IGNORE
)



FINAL TABLE
IDBESCHREIBUNGISTMENGESOLLMENGE
102Nagel40005000
201Hammer55
202Zange20
203Zange MK205

Jetzt sehe ich zumindest schon das Gesamtergebnis. (Die Zeile mit der ID 202 taucht jetzt in der Ergebnismenge auf, weil der DELETE nicht mehr da ist. Darum wird für diese Zeile durch den jetzt aktivben UPDATE der SOLLWERT auf 0 gesetzt). Und wenn ich jetzt für jede dieser Zeilen noch wissen will, was genau passiert ist? Auch dafür gibt es eine Lösung. Mit der INCLUDE-Anweisung kann ich weitere Spalten in das MERGE-Statement einbringen. Diese Spalten verhalten sich so, als wären sie Bestandteil der Zieltabelle. Sie können daher beim Update in der SET-Klausel gesetzt werden und beim INSERT mit der VALUES-Klausel gefüllt werden. Wird beim INSERT ohne eine Spaltenliste gearbeitet, dann muss sogar ein Wert für diese Spalte mitgegeben werden, sonst gibt es einen SQLCODE=-117. Die INCLUDE-Klausel funktioniert nur in Verbindung mit dem SELECT FROM FINAL TABLE. Das Ganze sieht dann so aus:

SELECT * FROM FINAL TABLE (
MERGE INTO WARENBESTAND AS WB
INCLUDE (ART CHAR(6))
USING ( SELECT ID,SOLLMENGE,BESCHREIBUNG FROM DIREKTIVE WHERE SOLLMENGE IS NOT NULL OR BESCHREIBUNG IS NOT NULL ) AS DIR
ON ( WB.ID = DIR.ID )
WHEN MATCHED THEN UPDATE SET WB.SOLLMENGE = COALESCE(DIR.SOLLMENGE,WB.SOLLMENGE), WB.BESCHREIBUNG=COALESCE(DIR.BESCHREIBUNG,WB.BESCHREIBUNG), ART='UPDATE'
WHEN NOT MATCHED THEN INSERT (ID,BESCHREIBUNG,ISTMENGE,SOLLMENGE,ART) VALUES(DIR.ID,DIR.BESCHREIBUNG,0,DIR.SOLLMENGE,'INSERT')
ELSE IGNORE
)

Die Ergebnismenge wird dann um diese Spalte erweitert:

FINAL TABLE
IDBESCHREIBUNGISTMENGESOLLMENGEART
102Nagel40005000UPDATE
201Hammer55UPDATE
202Zange20UPDATE
203Zange MK205INSERT


Zurück zu DB2 Home Impressum / Datenschutz