Oraculix

Geschichten aus dem Alltag der Oracle Datenbank-Programmierung und -Administration

Subqueries sparen mit der WITH-Clause

Verfasst von oraculix am 21. Oktober 2009

Es gibt ja so das eine oder andere Feature in umfangreichen Softwareprodukten, das schon geraume Zeit auf dem Markt ist, in der Praxis aber wenig Beachtung findet, obwohl es ein paar nützliche Einsatzgebiete dafür gäbe. Unter Oracle SQL ist so ein Feature nach meiner Erfahrung die „WITH-Klausel“.

Seit Oracle 9i wurde die SQL-Syntax um die WITH-Klausel erweitert. Diese Klausel kann man sich wie eine Materialized View vorstellen, die im Laufe eines SQL-Statements mehrfach referenziert werden kann.

Damit lässt sich in SQL-Code, der bisher mehrere identische Sub-Selects enthielt, der Code deutlich reduzieren (was ihn übersichtlicher und wartungsfreundlicher macht) und damit oft auch Leistung gewinnen, indem diese Sub-Selects weniger ausgeführt werden. Letzteres hängt vom Optimizer ab.

Früher hätte man in solchen Fällen möglicherweise die Ergebnisse einer aufwendigen Unterabfrage in eine temporäre Tabelle ausgelagert und das äußere SQL dann auf die temporäre Tabelle zugreifen lassen. Nun entscheidet sich der Optimizer zwischen dem internen Umschreiben des SQLs oder der temporären Materialisierung.

Beispielszenario

Da ich das Beispiel aus der Oracle-Doku nicht einfach kopieren und lieber Mehrwert liefern will, hier ein etwas unterschiedliches Szenario:

  • Wir wollen aus einer Tabelle „depot_kunde“ Daten kopieren, und zwar in Abhängigkeit von zwei Listen in den Tabellen „tmp_uid“ und „tmp_dep“.
  • Sind Schlüssel aus dem Datensatz in einer der beiden Listen enhalten, soll der Datensatz kopiert werden.
  • Sind Schlüssel aus dem Datensatz in beiden Listen enhalten, soll der Datensatz nicht kopiert werden.

Dazu gibt es etliche, mögliche Ansätze, hier soll ein nicht so effizienter gezeigt werden, um den Vorteil der WITH-Klausel zu demonstrieren:

INSERT INTO depot_kunde_kopie
SELECT *
  FROM depot_kunde v
 WHERE v.kunde_uid IN( SELECT a.kunde_uid
                         FROM depot_kunde a
                         LEFT OUTER JOIN tmp_uid b ON a.kunde_uid = b.uid
                         LEFT OUTER JOIN tmp_dep c ON a.depot_nr = c.depot_nr
                        WHERE b.uid IS NULL
                           OR c.depot_nr IS NULL )
    OR v.depot_nr IN( SELECT a.depot_nr
                        FROM depot_kunde a
                        LEFT OUTER JOIN tmp_uid b ON a.kunde_uid = b.uid
                        LEFT OUTER JOIN tmp_dep c ON a.depot_nr = c.depot_nr
                       WHERE b.uid IS NULL
                          OR c.depot_nr IS NULL )a

Wie man sieht, ist das Subselect für die Suche nach den Schlüsseln „kunde_uid“ und „depot_nr“ identisch. Die Untersuchung mit Autotrace für einen Testdatenbestand zeigt 14052419 Consistent Gets an.

Wir lagern das Subselect nun in den WITH-Bereich aus:

INSERT INTO depot_kunde_kopie
WITH sub AS
     ( SELECT a.kunde_uid
            , a.depot_nr
         FROM depot_kunde a
         LEFT OUTER JOIN tmp_uid b ON a.kunde_uid = b.uid
         LEFT OUTER JOIN tmp_dep c ON a.depot_nr = c.depot_nr
        WHERE b.uid IS NULL
           OR c.depot_nr IS NULL )
SELECT *
  FROM depot_kunde v
 WHERE v.kunde_uid IN( SELECT kunde_uid FROM sub )
    OR v.depot_nr IN( SELECT depot_nr FROM sub );

Das Autotrace für den Testdatenbestand zeigt nur noch 2605575 Consistent Gets an, d.h. der Ressourcenbedarf ist an dieser Stelle um den Faktor 5,4 besser!

Wie schon oben erwähnt, entscheidet der Optimizer, wie er mit den Anweisungen in der WITH-Klausel umgehen soll. In etlichen Fällen kann das auch bedeuten, daß die Performance nicht besser wird, möglicherweise sogar schlechter, wie in diesem Blogbeitrag beschrieben. Es empfiehlt sich also, immer Vergleichstests zu machen oder wenigstens den Explain Plan zu vergleichen.

Weblinks

Tip: Referencing the Same Subquery Multiple TimeTip: Referencing the Same Subquery Multiple Time

Veröffentlicht in Oracle, Performance, SQL, Unterbewertet | 1 Kommentar »

Anmerkungen zu INSERT ALL

Verfasst von oraculix am 2. Oktober 2009

Seit Oracle 9i gibt es die Option des sogenannten „Multi-Table Insert“, mit dem die bekannte INSERT … SELECT … – Syntax so erweitert wurde, daß die Ergebnisse aus dem SELECT auf mehrere Tabellen verteilt werden können. Ein paar Beispiele stehen in der Oracle-Doku.

INSERT ALL und Sequences

Nicht ganz intuitiv ist die Verwendung von Sequences: Soll z.B. in einer Master-Detail-Beziehung eine ID aus einer Sequence mehrfach verwendet werden, so kommt nicht CURRVAL zum Einsatz, sondern es muß überall NEXTVAL verwendet werden:

INSERT ALL
      INTO master_tab ( id, kunde_nr, ... )
    VALUES ( master_seq.NEXTVAL, ... )
      INTO detail_tab ( master_id, detail_nr, ... )
    VALUES ( master_seq.NEXTVAL, 1, ... )
    SELECT kunde_nr
      FROM tmp_aktive_kunden;

Bugs mit Master-Detail-Tabellen

Wenn man das obige Beispiel einmal ausprobiert, kann es durchaus vorkommen, daß es abbricht, wenn eine Fremdschlüsselbeziehung zwischen den Tabellen vorliegt.

ORA-02291: integrity constraint violated - parent key not found

Es handelt sich dabei um den seit 2003 bis heute noch nicht behobenen Bug 2891576. Dieser Bug lässt sich umgehen, indem das vorhandene Constraint entweder deaktiviert oder mit Deferred Constraints gearbeitet wird.

Allerdings gibt es auch beim Workaround einen Wermutstropfen: In einem Szenario mit zwei Tabellen von je etwa 2 Millionen Datensätzen und einem Insert von je 400000 Datensätzen kam es auf einem meiner Testsysteme (10.2.0.3, HP-UX 11.31 Itanium) zu sehr langen Laufzeiten, wenn Deferred Constraints verwendet wurden. Ohne Constraint war das Statement in Sekunden durch. Eine Klärung durch Oracle steht noch aus, ich werde den Artikel dann ergänzen.

Weblinks

Veröffentlicht in Oracle, Performance, SQL, Troubleshooting | Kommentar schreiben »

Datenverlust über Datenbank-Link

Verfasst von oraculix am 9. September 2009

Gerade bin ich mal wieder auf ein altes, aber stets aktuelles Thema gestoßen worden: Internationalisierung („Globalization“ im Oracle-Sprech).

In diesem speziellen Fall geht es um ein Szenario, bei dem Daten über einen DB-Link abgefragt werden. Dabei gehen einige Sonderzeichen verloren, die in der Quell-DB aber zu sehen sind. Hier eine kurze Zusammenfassung des Szenarios:

  1. DB „ORC1″ hat einen DB-Link zu „ORC2″.
  2. Tabellenspalte „ename“ enthält Sonderzeichen, z.B. „´“ (Akut).
  3. Abfrage der Spalte aus ORC1 mit „SELECT ename FROM emp@orc2;“
  4. Das Sonderzeichen wird bei dieser Abfrage nicht angezeigt.
  5. ORC1 hat den Zeichensatz WE8ISO8859P15, ORC2 WE8ISO8859P1.

Der Grund, weshalb das Sonderzeichen auf dem Weg über den Link verloren geht, liegt in den unterschiedlichen Zeichensätzen. Metalink Note 121627.1 beschreibt die Unterschiede und zeigt, daß im neueren P15-Zeichensatz einige Zeichen des P1-Zeichensatzes (z.B. gegen das Euro-Symbol) ersetzt wurden.

Eine kleine Aufstellung der Unterschiede zwischen den beiden Zeichensätzen:

Hex  |  WE8ISO8859P1 (ISO Latin 1)  |  WE8ISO8859P15 (ISO Latin 9)
Code |  Beschreibung                |  Beschreibung
-----+------------------------------+------------------------------------
A4   |  Währungssymbol (¤)          |  EURO-Symbol (€)
     |                              |
A6   |  Vertikaler Balken (¦)       |  Großes S mit Hatschek (Š)
     |                              |
A8   |  Trema (¨)                   |  Kleines s mit Hatschek (š)
     |                              |
B4   |  Akut  (´)                   |  Großes Z mit Hatschek (Ž)
     |                              |
B8   |  Cedille  (¸)                |  Kleines z mit Hatschek (ž)
     |                              |
BC   |  Ein Viertel (¼)             |  Große OE-Ligatur (Œ)
     |                              |
BD   |  Einhalb  (½)                |  Kleine oe-Ligatur (œ)
     |                              |
BE   |  Drei Viertel  (¾)           |  Großes Y mit Trema (Ÿ)
-----+------------------------------+------------------------------------

Bleibt die Frage nach einer Lösung. Die könnte in der Gegenfrage bestehen, ob man einen allein stehenden Akut denn benötigt und überhaupt zulassen sollte.

Andernfalls ist eine Angleichung der Zeichensätze angesagt. Da die Änderung eines DB-Zeichensatzes mit einigem Aufwand verbunden ist, sollte überlegt werden, gleich auf Unicode umzusteigen – der beherrscht in Version 4 immerhin 96.382 Zeichen.

Wer die Ergebnisse einer Zeichenkonvertierung auf die Schnelle prüfen will, kann das übrigens mit der SQL-Funktion „convert“:

SELECT CONVERT( '`Hallo Welt´', 'WE8ISO8859P15','WE8ISO8859P1' )
  FROM DUAL;

Weblinks

Veröffentlicht in Oracle, SQL, Troubleshooting | Verschlagwortet mit : , , | Kommentar schreiben »