Arbeiten mit großen Datenbanken Datenbank 11g macht den FULL OUTER JOIN tauglich für das Data Warehouse Autor: Karol Hajdu, Trivadis AG Den FULL OUTER JOIN gibt es in Oracle Database seit der Version 9.0. Doch erst mit der Version 11g steht seinem Einsatz in der Data-Warehouse-Praxis nichts mehr im Wege. Da die meisten Data Warehouses eine Versionierung der Stammdaten implementiert haben, und obwohl diese Versionierung mit einem FULL OUTER JOIN elegant umsetzbar ist, findet dieses Konstrukt in der Praxis keine breite Anwendung. Der Grund liegt darin, dass die Oracle-interne Ausführung eines FULL OUTER JOINS äußerst ineffizient ist. So ist der praktische Einsatz bei großen Tabellen so gut wie unmöglich. Oracle Database 11g bietet eine neue interne Implementation, die diesen Nachteil beseitigt. Die in diesem Artikel beschriebenen Konzepte werden anhand des folgenden Beispiels erklärt: In der Staging-Area des Data Warehouses liegt die Tabelle SA_CUSTOMER. Die Spalte OLTPKey bildet den Primärschlüssel der Tabelle. Zusätzlich gibt es noch eine inhaltliche Spalte namens Status, die den Status des Kunden abbildet. Des Weiteren existiert im CORE des Data Warehouses eine Tabelle C_CUSTOMER mit der Spalte SurrogateKey als Primärschlüssel. Neben der inhaltlichen Spalte Status bestehen noch die beiden versionierungstechnischen Felder C_ValidFrom und C_ValidTo. Abbildung 1 zeigt die Tabellenstruktur und den Tabelleninhalt des Beispiels. Der FULL OUTER JOIN Streng genommen ist der FULL OUTER JOIN ein Mengen-Operator der relationalen Algebra. Die meisten Abbildung 1: Tabellen SA_CUSTOMER und C_CUSTOMER Anbieter der relationalen Datenbank-Technologien haben ihn in den letzten zehn Jahren in ihre DatenbankServer integriert. Im Gegensatz zu einem INNER JOIN liefert ein FULL OUTER JOIN nicht nur solche Rows aus beiden Tabellen zurück, die die gleichen Werte-Kombinationen für die Join-Spalten ausweisen, sondern auch alle restlichen Rows aus beiden Tabellen, in denen keine Übereinstimmung gefunden werden konnte (siehe Abbildung 2). Mit einem FULL OUTER JOIN kann man zum Beispiel die Versionierung der Stammdaten im Data Warehouse elegant umsetzen. Bei der Versionierung geht es darum, die Änderungen seit dem letzten Datenabgleich zu erkennen und dieses ermittelte Delta in geeigneter Form in dem CORE Data Warehouse abzulegen. SELECT SA.*, C.* FROM SA_CUSTOMER SA FULL OUTER JOIN C_CUSTOMER C ON SA.OLTPkey = C.OLTPkey ; Abbildung 2: Unterschied zwischen INNER JOIN und FULL OUTER JOIN 46 www.doag.org Arbeiten mit großen Datenbanken Mit einem FULL OUTER JOIN wird die Delta-Erkennung sehr einfach und lesbar umgesetzt (siehe Listing 1) CREATE TABLE H_CUSTOMER_DELTA AS SELECT * FROM (SELECT C.SurrogateKey , SA.OLTPkey , SA.Status ,CASE WHEN SA.OLTPkey IS NULL THEN 1 ELSE 0 END AS DML_D ,CASE WHEN C.OLTPkey IS NULL THEN 1 ELSE 0 END AS DML_I ,CASE WHEN SA.OLTPkey IS NOT NULL AND C.OLTPkey IS NOT NULL AND (SA.STATUS <> C.STATUS) --Status is mandatory THEN 1 ELSE 0 END DML_U FROM (SELECT * FROM C_CUSTOMER WHERE C_ValidTo = to_date(‚01.01.9999‘, ‚dd.mm.yyyy‘)) C FULL OUTER JOIN SA_CUSTOMER SA ON SA.OLTPkey = C.OLTPkey ) WHERE DML_I+DML_U+DML_D > 0 ; Listing 1: Delta-Erkennung mit Einsatz von FULL OUTER JOIN NLSRTL Version 10.2.0.3.0 – Production SQL> SELECT C.SurrogateKey 2 , SA.OLTPkey 3 , SA.Status 4 ,CASE WHEN SA.OLTPkey IS NULL 5 THEN 1 ELSE 0 END AS DML_D 6 ,CASE WHEN C.OLTPkey IS NULL 7 THEN 1 ELSE 0 END AS DML_I 8 ,CASE WHEN SA.OLTPkey IS NOT NULL 9 AND C.OLTPkey IS NOT NULL 10 AND (SA.STATUS <> C.STATUS) --Status is mandatory 11 THEN 1 ELSE 0 END DML_U 12 FROM (SELECT * FROM C_CUSTOMER 13 WHERE C_ValidTo = to_ date(‚01.01.9999‘, ‚dd.mm.yyyy‘)) C 14 FULL OUTER JOIN SA_CUSTOMER SA 15 ON SA.OLTPkey = C.OLTPkey; Execution Plan -----------------------------------Plan hash value: 3478700539 SQL> SELECT * FROM H_CUSTOMER_DELTA; SURROGA TEKEY OLTPKEY STATUS DML_D DML_I DML_U ----- ------- ------ ----- ----- ---6128 B active 0 0 1 C inactive 0 1 0 734 1 0 0 Listing 2: Resultat der Delta-Erkennung anhand der Beispielsdaten Der FULL OUTER JOIN war bis zur Version 10g praktisch nicht einsetzbar. Den Grund kann man relativ einfach finden, indem man sich den Execution Plan für ein Statement mit FULL OUTER JOIN im Listing 3 anschaut. SQL> select * from v$version ; BANNER --------------------------------------Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Prod PL/SQL Release 10.2.0.3.0 - Production CORE 10.2.0.3.0 Production TNS for Linux: Version 10.2.0.3.0 - Production --------------------------------------| Id | Operation | Name | ------------------------------------| 0 | SELECT STATEMENT | | 1 | VIEW | | 2 | UNION-ALL | |* 3 | HASH JOIN OUTER | |* 4 | TABLE ACCESS FULL| C_CUSTOMER | 5 | TABLE ACCESS FULL| SA_CUSTOMER |* 6 | HASH JOIN ANTI | Listing 3: Execution Plan für ein FULL OUTER JOIN in Oracle 9i/10g Man sieht, dass jede der Tabellen zweimal gelesen wird. Solange die Tabellen klein sind, ist dies zu vernachlässigen. Handelt es sich jedoch um große Tabellen, komplexe oder aufwändige Joins, Groupbys oder Sorts, die den Input für den FULL OUTER JOIN aufbereiten, so ist das Resultat verheerend. Eine Verdoppelung der Arbeit hat dann einen fatalen Einfluss auf die Verarbeitungszeit. Dies trifft vor allen für Data-Warehouse-Problemstellungen zu und erklärt somit, warum ein FULL OUTER JOIN in Oracle 9i/10g hier nur sehr selten eine DOAG News Q2-2008 47 Arbeiten mit großen Datenbanken praktische Anwendung fand. Ein Blick in den Execution Plan zeigt die Verbesserungen in Oracle 11g am FULL OUTER JOIN (siehe Listing 4). SQL> select * from v$version BANNER --------------------------------------Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production PL/SQL Release 11.1.0.6.0 - Production CORE 11.1.0.6.0 Production TNS for Linux: Version 11.1.0.6.0 - Production NLSRTL Version 11.1.0.6.0 - Production SQL> SELECT C.SurrogateKey 2 , SA.OLTPkey 3 , SA.Status 4 ,CASE WHEN SA.OLTPkey IS NULL 5 THEN 1 ELSE 0 END AS DML_D 6 ,CASE WHEN C.OLTPkey IS NULL 7 THEN 1 ELSE 0 END AS DML_I 8 ,CASE WHEN SA.OLTPkey IS NOT NULL 9 AND C.OLTPkey IS NOT NULL 10 AND (SA.STATUS <> C.STATUS) --Status is mandatory 11 THEN 1 ELSE 0 END DML_U 12 FROM (SELECT * FROM C_CUSTOMER 13 WHERE C_ValidTo = to_ date(‚01.01.9999‘, ‚dd.mm.yyyy‘)) C 14 FULL OUTER JOIN SA_CUSTOMER SA 15 ON SA.OLTPkey = C.OLTPkey; Execution Plan -------------------------------------Plan hash value: 1804996898 ---------------------------------------| Id | Operation | Name | ---------------------------------------| 0 |SELECT STATEMENT | | | 1 | VIEW | VW_FOJ_0 |* 2 | HASH JOIN FULL OUTER| | | 3 | VIEW | | |* 4 | TABLE ACCESS FULL | C_CUSTOMER | | 5 | TABLE ACCESS FULL | SA_CUSTOMER| ----------------------------------------Predicate Information (identified by operation id): ---------------------------------------- 2 - access(„SA“.“OLTPKEY“=“C“.“OLTPKEY“) 4 - filter(„C_VALIDTO“=TO_DATE(‚ 9999-0101 00:00:00‘, ‚syyyy-mm-dd hh24:mi:ss‘)) Listing 4: Execution Plan für ein FULL OUTER JOIN in Oracle Database 11g Die gute Nachricht Oracle hat eine neue Row-Source-Operation implementiert. Sie heißt HASH JOIN FULL OUTER und liest die beiden Input-Tabellen nur einmal. Der praktische Einsatz in Data Warehouses ist daher mit Oracle 11g möglich. Doch wozu dienen die zwei Views – eine mit Namen VW_FOJ_0 und die andere ohne Namen? Diese Views sind nur temporär und im kompilierten Format vorhanden. Vermutlich dienen sie folgendem Zweck: Der Cost Based Optimizer von Oracle verwendet Predicate-Pushing und Subquery-Merging, um einen optimalen Execution-Plan zu finden, der die Daten-Einschränkungen (Filter) möglichst früh appliziert. Für einen FULL OUTER JOIN ist charakteristisch, dass diese Daten-Einschränkungen genau über der Row-Source appliziert werden, die im SELECT aufgeführt ist. Eine frühere oder spätere Applizierung der Filterbedingung, die im Falle vom INNER JOIN die Semantik der Abfrage nicht ändert, hätte im Falle von OUTER JOIN fatale Folgen: Der Execution Plan wäre vielleicht optimal, aber er würde falsche Resultate liefern (Issues bekannt mit Oracle Database 9i Version kleiner als 9.2.0.4). Würde beispielsweise der Filter „C_ValidTo = to_date(‚01.01.9999‘, ‚dd.mm.yyyy‘)“ aus Listing 1 erst nach dem FULL OUTER JOIN appliziert, dann liefert die Abfrage komplett andere Resultate. Findet diese Verschiebung nicht auf Anlass des Benutzers, sondern aufgrund der Optimierung statt (also aus Benutzersicht unbewusst), sind diese Resultate falsch. Hier kommt die Aufgabe der Views zum Tragen: Die Views enthalten vermutlich solche Hints, die dem Optimizer mitteilen, kein Predicate-Pushing oder Subquery-Merging anwenden zu dürfen. Kontakt: Karol Hajdu [email protected] Weitere Informationen zu den Themen der DOAG News finden Sie unter http://www.doag.org/ 48 www.doag.org