Das Modul generiert ein Datenbank-spezifisches Upsert (Update else Insert) Statement auf Basis der von der Entwickler eingegebenen SQL-Abfrage und führt das effektive Statement aus. Die Zieltabelle wird mit der SQL-Abfrage über definierte Schlüssel abgeglichen. Die Zeilen mit Schlüsseln die in der Zieltabelle als auch in der SQL-Abfrage vorhanden sind werden auf die Werte der SQL-Abfrage gesetzt (Update Funktionalität). Zeilen mit Schlüsseln, die in der SQL-Abfrage vorkommen aber nicht in der Zieltabelle, werden in die Zieltabelle eingefügt (Insert Funktionalität).
Name | Bedeutung |
---|---|
Modul | Upsert |
Modulklasse | DsModTemplateSQL |
Typ | Java |
Zweck | Basierend auf einen definierten Primärschlüssel sollen die Daten der angegebenen SQL-Abfrage in die Zieltabelle integriert werden (Update else Insert = Upsert) |
Transformationscode | SQL Abfrage |
Quellen | Quelltabellen in einer Datenbank |
Ziele | Zieltabelle in derselben Datenbank |
Da nicht alle Datenbanken einen einheitlichen SQL MERGE Befehl haben wird abhängig von der beteiligten Datenbank ein Statement generiert, welches die Upsert Funktionalität durchführt.
/ Die SQL Abfrage und die Zieltabelle sind nach der Ausführung nicht identisch, da Zeilen, die nicht in der Quellabfrage sind, nicht aus der Zieltabelle gelöscht wurden. /
Das folgende Beispiel zeigt, wie man mit Hilfe des Moduls eine Upsert Anweisung aufbauen kann. Zunächst die verwendete SQL-Abfrage:
SELECT n.n_nationkey AS nation_key
, n.n_name nation_name
, r.r_regionkey AS region_key
, r.r_name region_name
FROM stage.region r
, stage.nation n
WHERE 1 = 1
AND n.n_regionkey = r.r_regionkey
Als Primärschlüssel wird "nation_key" angegeben (siehe hierzu Attribute).
Heißen das Zielschema "data_mart" und die Zieltabelle "d_nation", generiert das Modul dazu unter Postgres das folgende, effektive SQL:
INSERT INTO data_mart.d_nation AS tgt (
nation_key
, nation_name
, region_key
, region_name
)
SELECT nation_key
, nation_name
, region_key
, region_name
FROM (
SELECT n.n_nationkey AS nation_key
, n.n_name nation_name
, r.r_regionkey AS region_key
, r.r_name region_name
FROM stage.region r
, stage.nation n
WHERE 1 = 1
AND n.n_regionkey = r.r_regionkey
) src
ON CONFLICT (nation_key)
DO UPDATE
SET nation_name = excluded.nation_name
, region_key = excluded.region_key
, region_name = excluded.region_name
WHERE NOT (excluded.nation_name IS NOT DISTINCT FROM tgt.nation_name
AND excluded.region_key IS NOT DISTINCT FROM tgt.region_key
AND excluded.region_name IS NOT DISTINCT FROM tgt.region_name
)
Wie im Beispiel zu erkennen, müssen die Spaltennamen in der Quellabfrage als Aliase angegeben werden, damit sie den Spaltennamen der Zieltabelle entsprechen. Dieses entfällt natürlich, wenn die Abfragespalte bereits so heißt, wie die entsprechende Spalte im Ziel.
Im Falle vom Update prüft datasqill, ob sich etwas geändert hat. Diese Prüfung muss dabei berücksichtigen, dass Spalten, die sowohl in der Quelle als auch im Ziel NULL sind, als gleich gelten. Beim Postgres wurde in diesem Beispiel der Operator "IS NOT DISTINCT FROM" verwendet, um zu erkennen, dass die zwei Spalten identisch sind.
Bei einer Oracle Zieldatenbank sieht das generierte SQL so aus:
MERGE INTO data_mart.d_nation tgt
USING (
SELECT n.n_nationkey AS nation_key
, n.n_name nation_name
, r.r_regionkey AS region_key
, r.r_name region_name
FROM stage.region r
, stage.nation n
WHERE 1 = 1
AND n.n_regionkey = r.r_regionkey
) src
ON (src.nation_key = tgt.nation_key
)
WHEN MATCHED THEN
UPDATE
SET tgt.nation_name = src.nation_name
, tgt.region_key = src.region_key
, tgt.region_name = src.region_name
WHERE NOT (DECODE(src.nation_name, tgt.nation_name, 1, 0) = 1
AND DECODE(src.region_key, tgt.region_key, 1, 0) = 1
AND DECODE(src.region_name, tgt.region_name, 1, 0) = 1
)
WHEN NOT MATCHED THEN
INSERT (
nation_key
, nation_name
, region_key
, region_name
)
VALUES (
src.nation_key
, src.nation_name
, src.region_key
, src.region_name
)
Man kann erkennen, dass sowohl der zugrunde liegende DML-Befehl sich unterscheidet (INSERT oder MERGE), als auch das Verfahren, wie geprüft wird, ob sich die Datenspalten geändert haben.
Die Quellen des Moduls sind Tabellen, die über die vom datasqill Entwickler definierte Abfrage gelesen werden. Für diese Tabellen muss der datasqill Laufzeit-Benutzer Leserechte besitzen. Alle in der SQL-Abfrage verwendeten Quelltabellen müssen über die datasqill GUI im grafischen Datenmodell mit dem Eingang des Moduls verbunden werden.
Das Ziel des Moduls ist eine einzelne Tabelle. Für diese muss der datasqill Laufzeit-Benutzer Schreibrechte besitzen. Die Zieltabelle muss mit Hilfe der datasqill GUI im grafischen Datenmodell mit dem Ausgang des Moduls verbunden werden.
Im Auslieferungszustand bietet das Modul nur ein Attribut in der GUI für den datasqill Entwickler an:
Name | Typ | Bedeutung |
---|---|---|
Join Key Columns | Required Column List | Der Text "RL:" gefolgt von einer kommaseparierten Liste der Spalten, die für die Join-Bedingung verwendet werden sollen. Per Voreinstellung steht dort "RL:${tgt_pk_list}". Dabei wird die Variable tgt_pk_list durch die Liste der Primärspalten der Zieltabelle ersetzt |
Bei der Installation des Modules kann man weitere Attribute einstellen
Das Modul lässt sich derart erweitern, dass man das Attribut "Loop Query" aktiviert (siehe Einstellen von Modul-Attributen im Anhang):
Name | Typ | Bedeutung |
---|---|---|
Loop Query | SQL | Schleifenabfrage um die Ergebnisse in der Hauptabfrage zu verwenden. |
Dann hat man zusätzlich ein weiteres Attribut in dem man eine Query eingeben kann. Wenn man eine Loop-Query erfasst, dann führt das Modul pro gefundenen Datensatz die Hauptquery aus. Hierbei kann man die Spaltenergebnisse als Variablen verwenden.
Da die Hauptquery pro Ergebniszeile von der Loop-Query ausgeführt wird, sollte man die Ergebnismenge der Loop-Query gering halten. Sonst verliert man die Performanz von Mengenoperation und die Ausführung verhält sich prozedural.
Sehr sinnvoll kann man die Loop-Query auch als IF-Query verwenden. Z.B:
SELECT DISTINCT 1
FROM <source-table>
WHERE <last-change-date> >= <today>-7
Man bekommt eine Zeile, wenn sich seit 7 Tagen etwas geändert hat ansonsten keine. Somit wird die Haupt-Query einmal oder kein Mal ausgeführt.
In der Haupt-Query kann man die Spaltennamen der Loop-Query als Variablen verwenden. Es ist jedoch zu beachten, dass die Variablen jeweils als Text (String) eingefügt werden.
Siehe hierzu Beispiele beim Insert Modul.
Man kann technische Spalten definieren, die es ermöglichen, im Nachhinein zu sehen wann welche Zeilen eingefügt oder geändert wurden:
Name | Spaltendefinition | Bedeutung | Wert |
---|---|---|---|
ins_date | ta_inserted_ts | Eingefügt am | Wird bei einer Änderung nicht verändert. Beim Einfügen einer Zeile auf den aktuellen Zeitpunkt (current_date) gesetzt |
ins_by | ta_inserted_by | Eingefügt von | Wird bei einer Änderung nicht verändert. Beim Einfügen einer Zeile auf den aktuellen Änderer (current_by) gesetzt |
upd_date | ta_modified_ts | Geändert am | Wird mit dem aktuellen Zeitpunkt (current_date) befüllt |
upd_by | ta_modified_by | Geändert von | Wird mit dem aktuellen Änderer (current_by) befüllt |
del_date | ta_deletion_ts | Gelöscht am | Wird mit NULL befüllt |
del_by | ta_deleted_by | Gelöscht von | Wird mit NULL befüllt |
del_ind | ta_is_deleted | Löschkennzeichen | Wird mit 'N' befüllt |
current_date | ${get_current_datetime()} | Dieser Wert definiert den aktuellen Zeitpunkt | Im Auslieferungszustand der aktuelle Zeitpunkt in date-time (ohne Millisekunden) |
current_by | ${request_id} | Dieser Wert definiert den aktuellen Änderer | Im Auslieferungszustand die Request ID der datasqill Action Ausführung |
Die verwendeten Spaltennamen (unter Spaltendefinition) kann man ändern, sodass sie z.B. nicht mehr ta_inserted_ts heißt sondern insert_date. Siehe auch hierzu das Einstellen von Modul-Attributen im Anhang.
Enthät die Zieltabelle einige oder mehrere dieser technischen Spalten, so werden sie entsprechend der obigen Tabelle gefüllt. Ändert man im allerersten Beispiel die Zieltabelle "data_mart.d_nation" und fügt die technischen Spalten ta_inserted_ts, ta_inserted_by, ta_modified_ts, ta_modified_by, ta_deletion_ts, ta_deletion_by und ta_is_deleted hinzu, so wird unter Exasol folgendes generiert:
MERGE INTO data_mart.d_nation tgt
USING (
SELECT n.n_nationkey AS nation_key
, n.n_name nation_name
, r.r_regionkey AS region_key
, r.r_name region_name
FROM stage.region r
, stage.nation n
WHERE 1 = 1
AND n.n_regionkey = r.r_regionkey
) src
ON (src.nation_key = tgt.nation_key
)
WHEN MATCHED THEN
UPDATE
SET tgt.nation_name = src.nation_name
, tgt.region_key = src.region_key
, tgt.region_name = src.region_name
, tgt.ta_modified_by = 1804
, tgt.ta_modified_ts = DATE_TRUNC('second', CURRENT_TIMESTAMP)
, tgt.ta_is_deleted = 'N'
, tgt.ta_deletion_by = NULL
, tgt.ta_deletion_ts = NULL
WHERE NOT (DECODE(src.nation_name, tgt.nation_name, 1, 0) = 1
AND DECODE(src.region_key, tgt.region_key, 1, 0) = 1
AND DECODE(src.region_name, tgt.region_name, 1, 0) = 1
AND tgt.ta_is_deleted = 'N'
AND tgt.ta_deletion_by IS NULL
AND tgt.ta_deletion_ts IS NULL
)
WHEN NOT MATCHED THEN
INSERT (
nation_key
, nation_name
, region_key
, region_name
, ta_modified_by
, ta_modified_ts
, ta_inserted_by
, ta_inserted_ts
, ta_is_deleted
, ta_deletion_by
, ta_deletion_ts
)
VALUES (
src.nation_key
, src.nation_name
, src.region_key
, src.region_name
, 1804
, DATE_TRUNC('second', CURRENT_TIMESTAMP)
, 1804
, DATE_TRUNC('second', CURRENT_TIMESTAMP)
, 'N'
, NULL
, NULL
)