Posts mit dem Label PL/SQL werden angezeigt. Alle Posts anzeigen
Posts mit dem Label PL/SQL werden angezeigt. Alle Posts anzeigen

Freitag, Dezember 20, 2019

Caching von PL/SQL Funktionsaufrufen

Mohamed Houri zeigt in seinem Blog einen nützlichen Trick: er stellt den Fall einer Query vor, in der ein Wert mit dem Ergebnis eines Funktionsaufrufs verglichen wird:
a.xy_bat_id = f_get_id('BJOBD176')
Dieser Zugriff ruft beim Abrufen von 18605 Datensätzen aus der zugehörigen Tabelle 18605 recursive calls hervor - und darüber hinaus sehr viele consistent gets. Eine Untersuchung mit SQL Trace zeigt, dass fast die gesamte Laufzeit auf diesen wiederholt ausgeführten Funktionsaufruf entfällt. Offenbar wird hier für jeden Datensatz rekursiv die vollständige Query ausgeführt, die in der Funktion gekapselt ist. Um das Verhalten zu ändern, genügt es, den Aufruf in ein "select from dual" zu integrieren, also:
a.xy_bat_id = (select f_get_id('BJOBD176') from dual)
Damit wird ein "scalar subquery caching" hervorgerufen: dadurch dass die Funktion für den gegebenen Eingabewert im Rahmen der gleichen Query immer den gleichen Wert zurückgeben muss, kann der Optimizer das Ergebnis durch einmalige Ausführung bestimmen und für alle folgenden Datensätze wiederverwenden. Dieser Trick ist nicht neu - Mohamed verweist in diesem Zusammenhang auf Tom Kyte -, aber unter den entsprechenden Umständen kann er extrem nützlich sein.

Montag, September 10, 2018

Detailinformationen zu dbms_stats in 12c

Jonathan Lewis schreibt in seinem jüngsten Blog-Artikel über einige nützliche Funktionen, die zu dbms_stats in 12c ergänzt wurden:
  • report_stats_operations: liefert Basisinformationen zu den Erfassungsläufen der letzten n Tage, etwa die Start- und End-Zeit und die Anzahl erfolgreicher und fehlgeschlagener Tasks. Leider ist eine Filterung der Angaben nicht über Parameter möglich, sondern muss durch einen Pl/SQL Wrapper erledigt werden.
  • report_single_stats_operation: liefert Details zu einem der von der ersten Funktion gelieferten Tasks. Über das detail_level ALL Kann man dann auch die Ursache für eine fehlschlagende Erfassung finden.
So nützlich diese Informationen sind, würde ich mir doch eher entsprechende Dictionary Views wünschen, da die Funktionsausgaben keine weitere Bearbeitung erlauben - etwa, um die Laufzeit einer Operation aus Start- und Endzeit zu ermitteln. Aber ein nützliches Hilfsmittel sind die Funktionen in jedem Fall.

Mittwoch, November 16, 2016

dbms_redefinition und deterministische Funktionen

Vor längerer Zeit war ich hier zum Ergebnis gekommen, dass rdbms_redefinition für komplexere Umbaumaßnahmen nicht verwendbar ist, weil man im Mapping keine komplexere Join-Logik oder Subselects unterbringen kann. Jetzt habe ich bei Connor McDonald gesehen, wie man es richtig macht: statt eines Joins  kann man im col_mapping eine deterministische Funktion unterbringen - und damit wird das Package noch mal deutlich interessanter.

Montag, August 29, 2016

Umwandlung von LONG in CLOB mit SYS_DBURIGEN

Meine Standardantwort auf die Frage, wie man die Inhalte von LONG-Spalten auslesen kann, war seit vielen Jahren: ich hab's vergessen, aber Adrian Billington hat alles notiert, was man über diesen unerfreulichen Datentyp wissen muss. Diese Antwort kann ich jetzt modifizieren: der handlichste Weg, um LONGs in etwas weniger Häßliches zu verwandeln, ist die Verwendung der builtin-Funktion SYS_DBURIGEN, der von Marc Bleron (aka odi_63) in seinem Blog beschrieben wird. Intern erzeugt Oracle in diesem Zusammenhang ein DBURI Objekt und beim Aufruf der Funktion SYS_DBURIGEN(...).GETCLOB() wird eine SQL-Query generiert, die die erforderlichen Informationen abruft. Die Details des Aufrufs werde ich mir auch diesmal nicht merken, aber mit etwas Glück zumindest den Ort, an dem ich danach suchen kann.

Montag, August 01, 2016

Spaltenvergleichen mit NULL-Werten

Randolf Geist hat vor kurzem einen interessanten Artikel zu einem Thema veröffentlicht, mit dem man sich beim Schreiben komplexerer SQL-Queries regelmäßig herumschlagen muss: dem Vergleichen von Spalten, in denen NULL-Werte auftauchen können. Für die Prüfung der Gleichheit von Werten bedarf die korrekte Behandlung von NULL-Werten bereits eines recht sperrigen Ausdrucks:
column1 = column2 or (column1 is null and column2 is null)
Und noch unhandlicher wird der Ausdruck, wenn man die Ungleichheit von Werten prüfen möchte:
column1 != column2 or (column1 is null and column2 is not null) or (column1 is not null and column2 is null)
Um solche Konstrukte vermeiden zu können, wird bisweilen ein NVL um die Vergleichswerte gesetzt, um den möglichen NULL-Wert durch eine Alternative zu ersetzen, von der man sicher ist, dass sie außerhalb des Wertebereichs der Spalte liegt - aber wann kann man sich in einem solchen Punkt wirklich sicher sein?

Eine andere beliebte Variante dazu ist die Verwendung der lange Zeit nicht dokumentierten Funktion SYS_OP_MAP_NONNULL, die in 12c schließlich in der Doku erscheint, aber immer noch nicht im SQL language manual. Diese Funktion hat allerdings einen Nachteil: sie ergänzt ein Byte zu jedem Input-Wert, was dazu führt, dass sie bei Verwendung von Spalten mit der maximalen Größe für den verwendeten Datentyp einen Fehler hervorruft (nämlich "ORA-01706: user function result value was too large"). Als Alternative dazu wurde gelegentlich die Verwendung von decode vorgeschlagen - etwa von Stew Ashton, dessen zugehörigen Artikel ich hier gelegentlich erwähnt hatte. Da decode NULL-Werte als vergleichbar ansieht, werden die erforderlichen Prüfungen deutlich übersichtlicher - für den Fall der Gleichheit:
decode(column1, column2, 0, 1) = 0
Und für die Ungleichheitsprüfung:
decode(column1, column2, 0, 1) = 1
Hier kommt aber seit Version 11.2.0.2 eine problematische Optimierung ins Spiel: der Fall der Gleichheitsprüfung (aber nicht der der Ungleichheitsprüfung) wird intern auf die Verwendung von SYS_OP_MAP_NONNULL umgeschrieben, was dann wiederum die Probleme mit den Maximallängen hervorruft. Zusätzlich kommt noch hinzu, dass die SYS_OP_MAP_NONNULL-Funktion in Randolfs Test langsamer ist als das nicht umgeschriebene decode und langsamer als die verbose Standard-Variante. Insofern sollte man SYS_OP_MAP_NONNULL und die implizite Umschreibung von decode seit 11.2.0.2 unter Umständen besser vermeiden und fix control 8551880 verwenden, um die implizite Umwandlung zu vermeiden. Zu hoffen ist, dass Oracle gelegentlich eine solidere Lösung für dieses Problem zur Verfügung stellen kann.

Sonntag, April 10, 2016

STRING_SPLIT im SQL Server 2016

Vor einiger Zeit habe ich in der Sektion database ideas bei OTN folgenden Wunsch geäußert: a string splitting function like SPLIT_PART in postgres. SPLIT_PART erhält als Argumente einen String und einen Delimiter, zerlegt den String an den Positionen der Delimiter-Zeichen in Substrings und liefert den n-ten Teilstring:

SELECT SPLIT_PART('A;B;C;D', ';', 2);
split_part
-----------
B

Das ist sicherlich keine höhere Magie und kann in SQL auf verschiedenen Wegen erreicht werden (etwa durch den Einsatz regulärer Ausdrücke), aber ich war in der Vergangenheit oft genug in Situationen, in denen mir eine solche built-in-Funktion geholfen hätte, dass ich ihre Ergänzung in Oracle für wünschenswert halte.  Für den Vorschlag gab es 21 positive und 4 negative Stimmen (was bei OTN schon eine recht rege Beteiligung ist) und ich vermute mal, dass die Resonanz vielleicht noch positiver ausgefallen wäre, wenn ich im Titel auf "postgres" verzichtet hätte.

Heute habe ich dann im Blog von Ozar Unlimited einen Artikel von Erik Darling gelesen, der auf die Funktion STRING_SPLIT hinweist, die im SQL Server 2016 zur Verfügung steht und die folgendermaßen beschreiben wird: "splits input character expression by specified separator and outputs result as a table." Dort gibt's das jetzt also auch schon.

Donnerstag, März 31, 2016

PL/SQL Fehlerbehandlung

Ja, es ist richtig: meine Einträge hier werden in letzter Zeit kürzer und kürzer. Daran werde ich aber auch heute nichts ändern, denn eigentlich will ich gerade nur einen Link unterbringen: Steven Feuerstein listet in seinem Artikel Nine Good-to-Knows about PL/SQL Error Management- nun ja: neun interessante Punkte auf, die man beim Exception Handling in PL/SQL berücksichtigen sollte. Zu den wichtigsten Hinweisen gehören aus meiner Sicht:
  • "An exception raised does not automatically roll back uncommitted changes to tables." Hier ist also immer noch eine explizite Entscheidung erforderlich (und möglich).
  • "Whenever you log an error, capture the call stack, error code, error stack and error backtrace." Dazu kann man dbms_utility-Prozeduren einsetzen.
Die anderen sieben Punkte sind aber auch nicht uninteressant.

    Montag, Oktober 12, 2015

    Oracle-Package für HTTP/HTTPS

    Sayan Malakshinov hat zuletzt zwei Artikel veröffentlicht, in denen er sein GitHub Package XT_HTTP vorstellt, mit dessen Hilfe man auf HTTP- bzw. HTTPS-Seiten zugreifen kann, ohne Zertifikate importieren zu müssen. Die aktuelle Version enthält einen ergänzenden Timeout-Parameter, eine Suchoption auf Basis von regulären Ausdrücken (PCRE) und den Support für plsqldoc (ein Hilfsmittel zur automatischen Generierung von Dokumentation; ähnlich wie javadoc). Sicher sehr nützlich für entsprechende Fragestellungen.

    Der Artikel hat noch den zusätzlichen Pluspunkt, dass er im Beispiel belegt, dass ich meiner moralischen Verpflichtung zur Teilnahme am Oracle Database Developer Choice Award nachgekommen bin und tatsächlich auch zu den Up-Votern gehörte. Den Hintergrund zu dieser Bemerkung kann man bei Tim Hall finden - bzw. bei den bei ihm verlinkten Artikeln; und grundsätzlich geht es darum, dass die Teilnahme an der Abstimmung eine sehr kostengünstige Möglichkeit ist, sich bei den nominierten Mitgliedern der Oracle-Community zu bedanken. Wenn ich daran denke, wie viele gute Ideen ich mir im Laufe der Zeit bei Adrian Billington, Stew Ashton, Matthias Rogel und eben auch dem Herrn Malakshinov ausgeborgt habe (um nur einige zu nennen), dann ist ein Dank da durchaus nicht unangemessen...

    Freitag, April 17, 2015

    Table Functions (Steven Feuerstein)

    Wenn ich an PL/SQL denke, ist meine erste Assoziation dazu der Name Steven Feuerstein. Nun denke ich nicht furchtbar oft on PL/SQL, aber wenn der Herr Feuerstein über ein Thema schreibt, dem ich mit einer gewissen Regelmäßigkeit begegne, dann ist das allemal eine Verlinkung und Zusammenfassung der zugehörigen Artikel wert:
    • Table Functions: Introduction and Exploration, Part 1: skizziert die vorgesehene Struktur der Artikelserie und erläutert zunächst das Grundkonzept von table functions: der table operator wandelt eine collection in ein relationales Dataset um, so dass man darauf via Select zugreifen kann. Diese Datasets können als Grundlage in (parametrisierten) Views verwendet werden und mit anderen Tabellen/Datasets gejoint werden, man kann die Ergebnisse gruppieren, sortieren etc. In älteren Releases konnten nur nested tables und varrays vom table operator transformiert werden, aber seit 12.1 bieten auch integer-indexed associative arrays eine verwendbare Grundlage. Der Herr Feuerstein erwähnt die einschlägigen Artikel zum Thema von Adrian Billington und Tim Hall, auf die ich hier auch schon häufiger verwiesen habe. Es folgt eine Liste möglicher use cases (inklusive diverser Beispiele dazu):
      • Session-spezifische Daten mit Tabellendaten mergen, um die Vorteile Set-orientierter SQL-Operationen zu erhalten.
      • Programmatische Erzeugung spezieller Datasets für die Ausgabe.
      • Parametrisierte Views.
      • Parallelisierte Selects auf der Basis von pipelined table functions.
      • Verringerte PGA-Nutzung durch pipelined table functions, da die zugehörigen Collections nicht komplett im Speicher gehalten werden müssen, sondern sukzessive aufgebaut und weiterverarbeitet werden können.
    • Table Functions: Returning complex (non-scalar) datasets, part 2: geht über die im ersten Artikel vorgestellten Beispiel mit (skalaren) Einzelwerten hinaus und zeigt den Umgang mit rows, die mehrere unterschiedliche Attribute enthalten. Zunächst zeigt er dabei, wie man es besser nicht machen sollte (z.B. eine Table mit VARCHAR2(4000), deren Ergebnisse über String-Operationen zerlegt werden). Leider steht die plausiblere Lösung, einen PL/SQL record type als Datentyp einer nested table zu verwenden, nicht zur Verfügung, weil SQL an dieser Stelle etwas kleinlich ist. Stattdessen muss man einen object type verwenden, was syntaktisch kaum einen Unterschied macht.
    • Table Functions, Part 3a: table functions as parameterized views in the PL/SQL Challenge website: liefert - wie der Name schon andeutet - ein Beispiel für die Verwendung einer table function als parameterized view.
    • Table Functions, Part 3b: implementing table functions for PL/SQL Challenge reports: mit einem umfangreichen table function Beispiel.
    • Table Functions, Part 4: Streaming table functions: erklärt, wie man table functions in ETL-Operationen zur Abbildung komplexerer Transformationen einsetzen kann. Das Beispiel ist recht umfangreich, endet aber, ehe die pipelined table functions erreicht sind.
    Die folgenden Artikel versuche ich hier zu ergänzen, sobald sie folgen.

    Freitag, Juni 27, 2014

    PDF über PL/SQL erzeugen

    Zur Unterstützung meines unterstützungsbedürftigen Gedächtnisses: Morton Braten zeigt einige Möglichkeiten zur Erzeugung von PDF-Reports über PL/SQL.

    Mittwoch, Februar 05, 2014

    DBMS_REDEFINITION und Massenupdates

    Eine der von mir am häufigsten zitierten Antworten auf Oracle-Performance-Fragen ist Tom Kytes schöner Satz: "If I had to update millions of records I would probably opt to NOT update." Er bezieht sich darauf, dass Massenupdates eine sehr teure Operation darstellen, da sie Änderungen an jedem betroffenen Block und große Menge von redo und undo hervorrufen. Effizienter ist stattdessen der Neuaufbau der geänderten Datenmenge über CTAS in einer Hilfstabelle und die anschließende Umbenennung der Objekte.

    Das Verfahren stößt allerdings an seine Grenzen, wenn es kein Wartungsfenster für die Durchführung des Austauschs gibt, da permanent DML-Operationen auf der fraglichen Tabelle durchgeführt werden. Für diesen Fall gibt es die Möglichkeit der Online-Reorganisation mit Hilfe von DBMS_REDEFINITION, die Tom Kyte ebenfalls gelegentlich erläutert hat. Dazu ein übersichtliches Beispiel:

    -- Session 1:
    drop table t;
    drop table t_redefine;
    
    create table t (id primary key, col_org, col2, padding)
    as
    select rownum id
         , 0 col_org
         , 0 col2
         , lpad('*', 50, '*') padding
      from dual
    connect by level <= 1000000;
    
    
    create table t_redefine (
        id number
      , col_upd number
      , col2 number
      , padding varchar2(50)
    );  
        
    
    declare
        l_colmap varchar(512);
      begin
        l_colmap := 'id, 1 col_upd, col2 + 2 col2, padding ';
    
        dbms_redefinition.start_redef_table
        (  uname           => user,
           orig_table      => 'T',
           int_table       => 'T_REDEFINE',
           orderby_cols    => 'ID',
           col_mapping     => l_colmap );
     end;
    /
    
    -- Session 2:   
    update t set col2 = col2 + 2 where id <= 5;
    
    select id, col_org, col2 from t where id <= 10;
    
            ID    COL_ORG       COL2
    ---------- ---------- ----------
             1          0          2
             2          0          2
             3          0          2
             4          0          2
             5          0          2
             6          0          0
             7          0          0
             8          0          0
             9          0          0
            10          0          0
    
    -- Session 1:  
    exec dbms_redefinition.finish_redef_table ( user, 'T', 'T_REDEFINE' );
    --> wartet auf einen Abschluss der offenen Transaktion gegen T
    
    -- Session 2:
    commit;
    -- Session 1:  
    --> der Aufruf von dbms_redefinition.finish_redef_table meldet Vollzug
    
    -- Session 2:
    select id, col_upd, col2 from t where id <= 10;
    
            ID    COL_UPD       COL2
    ---------- ---------- ----------
             1          1          4
             2          1          4
             3          1          4
             4          1          4
             5          1          4
             6          1          2
             7          1          2
             8          1          2
             9          1          2
            10          1          2
    

    Der Test definiert eine Quelltabelle T, deren Spalte col_org durch eine Spalte col_upd ersetzt werden soll, außerdem wird der Wert für col2 behutsam erhöht. Dazu wird eine Hilfstabelle T_REDEFINE mit den gewünschten Spalten angelegt und anschließend die Prozedur dbms_redefinition.start_redef_table aufgerufen, der neben den Angaben der Ziel- und der Hilfstabelle ein column_mapping übergeben wird, das die inhaltliche Füllung der Spalten nach der Reorganisation definiert. In diesem Mapping sind leider keine skalaren Subqueries erlaubt - wenn man versucht l_colmap mit der Angabe:
    l_colmap := 'id, (select 2 from dual) col_upd, padding ';
    zu füllen, erhält man den Fehler
    ORA-22818: Unterabfrage-Ausdrücke sind hier nicht zulässig
    Demnach können im Rahmen der Redefinition offenbar keine komplexeren Join-Operationen zur Füllung einer verändert definierten Spalte eingesetzt werden. Möglich ist allerdings die Ableitung von Spalten (col2 + 2). Änderungen der Quelltabelle, die nach dem Start der Redefinition in anderen Sessions durchgeführt wurden, werden korrekt propagiert. Die technische Erklärung des Verhaltens liefert ein SQL-Trace, dem zu entnehmen ist, dass zur Tabelle T eine Materialized View T_REDEFINE mit fast refresh erzeugt wird:

    create snapshot "TEST"."T_REDEFINE"   on prebuilt table with reduced 
      precision  refresh fast with primary key  as select id, 1 col_upd, col2 + 2 
      col2, padding  from "TEST"."T"   "T"
    
    ...
    
    INSERT /*+ BYPASS_RECURSIVE_CHECK APPEND  */ INTO "TEST"."T_REDEFINE"("ID",
      "COL_UPD","COL2","PADDING") SELECT "T"."ID",1,"T"."COL2"+2,"T"."PADDING" 
      FROM "TEST"."T" "T" ORDER BY ID
    

    Die aus der Quelltabelle T übernommenen Spalten werden somit aktualisiert und parallel durchgeführte Änderungen sind im Zielobjekt weiterhin sichtbar. Nicht möglich scheint allerdings die Aktualisierung unter Zuhilfenahme einer Referenz zu sein, die beim Massenupdate über CTAS zu meinen präferierten Vorgehensweisen gehört. Ein großer Vorteil von dbms_redefinition ist allerdings, dass die Prozeduren des Packages sich um die Behandlung abhängiger Objekte (Indizes, Trigger etc.) kümmern können.

    Nachtrag 16.11.2016: Connor McDonald zeigt, wie man die hier nicht verwendbare Join-Operation durch eine deterministische Funktion ersetzen kann: damit wird dbms_redefinition dann extrem wertvoll.

    Samstag, Januar 18, 2014

    Schnelle Datensatz-Generierung

    Im OTN Forum hat sich dieser Tage ein Thread mit einer recht interessanten Diskussion zum Thema der schnellsten Möglichkeit zur Erzeugung einer großen Mengen von Test-Datensätzen ergeben. Ausgangspunkt war, dass der Fragesteller des Threads mit der Performance eines INSERT mit einer connect by level Generierung von 10 Millionen Datensätzen nicht zufrieden war und nach schnelleren Optionen suchte. David Berger schlug eine PL/SQL-Lösung mit Collections und Bulk Insert vor, die tatsächlich im System des OP zu einer deutlichen Beschleunigung führte, und erklärte "There are cases where the PL/SQL can be faster then a pure SQL". Obwohl ich der Meinung bin, dass SQL fast immer die schnellere Lösung liefert, will ich der Aussage nicht grundsätzlich widersprechen, da man entsprechende Fälle gewiss konstruieren kann, hatte aber die Vermutung, dass im gegebenen Fall auch schnelle SQL-Lösungen möglich sein sollten und die schlechte Performance der connect by level Variante auf der Größe der Level-Angabe beruhte - entsprechend der Ausführungen von Tanel Poder, der vor einigen Jahren auf die großen UGA-Belastungen durch die massiven Rekursionen hinwies und alternativ eine Lösung mit einem cartesischen Produkt kleiner Generator-Queries vorschlug. Diese Variante brachte im System des OP allerdings nur eine geringfügige Beschleunigung, was mich wunderte und Jonathan Lewis dazu brachte, das Thema genauer zu untersuchen. Später hat dann auch Randolf Geist zusätzliche Informationen beigesteuert. Hier folgen ein paar Punkte, die mir besonders erinnerungswürdig scheinen:
    • die PL/SQL-Lösung skaliert nicht, da sie den Aufbau der kompletten Ergebnismenge im Speicher erfordert, während die Speichernutzung der SQL-Variante mit cartesian joins moderat bleibt.
    • die im Testszenario des OP verwendete dbms_random-Funktion verursachte bei Jonathan Lewis ca. 85% der Laufzeit, weshalb er eine Parallelisierung des Funktionsaufruf vorschlug (oder auch den Verzicht auf den Aufruf und die Verwenung von rownum).
    • eine Parallelisierung der PL/SQL-Lösung wäre über ein Splitting der ursprünglichen Prozedur in mehrere Teile möglich, die jeweils bestimmte Datenbereiche abarbeiten. Entscheidend ist dabei, dass es nicht darum geht, das INSERT zu parallelisieren, sondern den teuren Funktionsaufruf.
    • bei der Parallelisierung der Funktionsaufrufe ist (natürlich) die Anzahl der CPUs entscheidend.
    • die Verwendung von ROWNUM scheint einen Bug bei der Parallelisierung hervorzurufen. Durch Vermeidung der Pseudo-Column konnte Randolf Geist die tatsächliche Parallelisierung der Operation deutlich verbessern.
    • Zur den Performance-Vorteilen von PL/SQL zu SQL auf seiner Workstation schreibt Jonathan Lewis: "My guess about the PL/SQL running faster (on my machine, at any rate) is that it's a measure of the inter-process communication costs of VMWare which appears in the SQL but doesn't appear when I run two independent serial processes for the pl/sql."
    Sollte der Thread noch weitere interessante Details hervorbringen, werde ich sie ergänzen.

    Mittwoch, September 25, 2013

    SQL_ID aus SQL_TEXT erzeugen

    Dass die SQL_ID als Hash für einen gegebenen SQL_TEXT erzeugt wird, konnte man vor längerer Zeit bei Tanel Poder erfahren. Jetzt hat Carlos Sierra eine handliche PL/SQL-Funktion veröffentlicht, mit der man die SQL_ID zu einer Query vor ihrer Ausführung berechnen kann (und im Artikel auch die Links auf den Artikel des Herrn Poder und eine entsprechende Python Funktion von Slavik Markovich aufgenommen).

    Dienstag, Juli 02, 2013

    Neue Features in 12c

    Während ich mühsam meine erste 12c-Installation auf Oracle Linux 6 zum Laufen gebracht habe (den ausführlichen Anleitungen des erschütternd sorgfältigen Dr. Hall folgend), sind andere Leute schon intensiv dabei, die Features des neuen Releases zu untersuchen. Hier ein paar Verweise auf interessante Untersuchungen:
    • WITH Clause Enhancements in Oracle Database 12c Release 1 (12.1): worin der bereits angesprochene Tim Hall die interessante Option der Verwendung von Funktionen und Prozeduren in CTEs untersucht. Dabei ist scalar subquery caching möglich, aber deterministische Funktionen im With werden leider beim wiederholten Aufruf nicht in gleicher Weise optimiert wie entsprechende Stand-alone Funktionen, wie Jonathan Lewis (und der im zugehörigen Scratchpad-Artikel kommentierende Sayan Malakshinov, der im eigenen Blog auch noch auf mögliche Inkonsistenzen in den Ergebnissen hinweist) gezeigt hat.
    • 12c - SQL Text Expansion: Tom Kyte stellt die Funktion dbms_utility.expand_sql_text vor, die eine Ersetzung von View-Namen durch die zugrunde liegenden Definitionen in SQL-Queries durchführt. Auch VPD-Effekte lassen sich damit offenbar deutlich leichter analysieren, als das bisher der Fall war.
    • 12c - Utl_Call_Stack...: noch mal der Herr Kyte, diesmal mit einem Hinweis auf das Package UTL_CALL_STACK, das eine deutlich komfortablere Analyse von Aufrufhierarchien gestattet, als das die alten dbms_utility.format%-Routinen gestatteten.
    • 12c - Invisible Columns...: wieder der Herr Kyte (der endlich wieder so viele Artikel für seinen Blog schreibt, wie ich mir das wünsche) mit Informationen zum Thema unsichtbarer Spalten, die im SELECT * nicht erscheinen, aber explizit angesprochen werden können, was als simplere Variante zur Edition Based Redefinition - EBR verwendet werden kann. Interessant ist dabei auch der Hinweis (eines weiteren Artikels im gleichen Blog), dass das Setzen einer Spalte als invisible diese ans Ende der Spaltenliste verschiebt (was aber offenbar keine physikalische Reorganisation, sondern nur eine Metadaten-Änderung bedeutet).
    • 12c: Intro To Multiple Indexes On Same Column List (Repetition): Richard Foote liefert ein Beispiel für die (hier schon mal erwähnte) Möglichkeit, die gleiche Spalte oder Spaltenkombination mit mehreren Indizes zu versehen, von denen allerdings immer nur einer als visible definiert sein darf. In seinem Beispiel zeigt er die Ersetzung eines als non-unique definierten Index durch einen als unique definierten (andere Fälle könnten bitmap vs. b*tree oder partitioned vs. non-partitioned sein). Nachtrag 06.07.2013: ein Beispiel von Tom Kyte gibt's dazu inzwischen auch.
    An dieser Stelle breche ich die Aufzählung willkürlich ab, obwohl es zuletzt noch jede Menge anderer interessanter Artikel zu neuen Features gab - denn das ist eine andere Geschichte, die ein andermal erzählt werden soll...

    Nachtrag 17.07.2013: viel ist zum Thema inzwischen erschienen, aber erwähnen will ich nur den Überblick zu den neuen Features in 12c in der deutschen DBA Community, der bei der Vorstellung der neuen Errungenschaften weniger eklektisch vorgeht, als ich das an dieser Stelle getan habe.

    Sonntag, Dezember 23, 2012

    String Aggregation in Oracle

    Philipp Salvisberg von Trivadis hat eine schöne Zusammenstellung diverser Optionen zur Zusammenfassung von String-Werten in einer konkatenierten Liste veröffentlicht - also jener Anforderung, für die Tom Kyte vor vielen Jahren die STRAGG-Funktion lieferte: der Verknüpfung der String-Werte einer Gruppe (etwa der Mitarbeiter eines Departments in der EMP-Tabelle) in einer Komma-separierten Liste. In dieser Zusammenstellung erscheinen verschiedene PL/SQL-Versionen, user-defined aggregate functions (des ODCIAggregate interface), XML-Varianten und schließlich die LISTAGG-Aggregat-Funktion aus 11.2, jeweils mit einer Angabe ihrer Verfügbarkeit in den Oracle-Releases und einem Performance-Vergleich (bei dem die XML-Lösungen schlecht und LISTAGG am besten abschneidet).

    Eine ähnliche Zusammenstellungen solcher String-Aggregationsfunktionen findet man auch bei Tim Hall, der außerdem noch die (undokumentierte) WM_CONCAT Funktion erwähnt und darüber hinaus auf eine von William Robertson vorgeschlagene Variante mit hierarchischen Queries und auf die Collect-Funktion, die Adrian Billington gelegentlich genauer erläutert hat, verweist.

    Freitag, September 07, 2012

    Returning Clause

    Carsten Czarski hat in seinem Blog eine kurze Erläuterung der RETURNING clause für DML-Operationen (INSERT, UPDATE und DELETE; leider nicht für MERGE) veröffentlicht. Auf der Suche nach ein paar weiteren Details zum Thema habe ich bei Rob van Wijk den Hinweis gefunden, dass man im RETURNING auch Aggregationen durchführen kann, um so z.B. die Anzahl behandelter Sätze pro Typ zu ermitteln.

    Mittwoch, Mai 09, 2012

    Basket Analysis mit DBMS_FREQUENT_ITEMSET

    Peter Scott zeigt im Rittman/Mead-Blog wie man eine Warenkorbanalyse mit Oracle-Bordmitteln durchführen kann: nämlich mit Hilfe des Packages DBMS_FREQUENT_ITEMSET. Damit kommt man dann zu solchen Klassikern wie Bier&Windeln oder Grabkerzen&Kukident Haftcreme.

    Freitag, März 30, 2012

    dbms_scheduler und die Sommerzeit

    Dieser Tage ist mir in einem Kundensystem folgendes Phänomen begegnet: mit Beginn der Sommerzeit verschoben sich einige - aber nicht alle - Scheduler-Jobs um genau eine Stunde nach hinten. Ganz offenbar hatte jemand vergessen, die Uhr umzustellen - aber wer? Und warum? Ein Blick in dba_scheduler_jobs zeigte, dass der Unterschied zwischen den Jobs, die die Sommerzeit korrekt behandelt hatten, und jenen, denen das nicht gelungen war, bei den Zeitangaben klar zu erkennen war:
    • die korrekte Zeit lieferten die Jobs mit Zeitzonenangabe, also z.B.: EUROPE/BERLIN
    • die falsche Zeit lieferten die Jobs mit Offset, also +01:00
    Was mir durchaus einleuchtet. Der feste Offset kann natürlich nur entweder zur Sommer- oder zur Winterzeit passen. Und wahrscheinlich waren alle Jobs, bei denen sich Verschiebungen ergeben hatten, erst nach dem Ende der letzten Sommerzeit definiert worden (oder niemand hatte die Verschiebung vorher bemerkt).

    Aber was führte zur unterschiedlichen Definition der Jobs? Nach einigem Ausprobieren (und Recherchieren) wurde klar, dass das start_date für die Zuordnung der Zeitzone verantwortlich ist. Die Dokumentation (11.2) sagt dazu: "The Scheduler retrieves the date and time from the job or schedule start date and incorporates them as defaults into the repeat_interval." Dazu ein Beispiel:

    exec dbms_scheduler.drop_job (job_name => 'test_mpr_job');
    
    begin
      dbms_scheduler.create_job (
        job_name        => 'test_mpr_job',
        job_type        => 'plsql_block',
        job_action      => 'begin null; end;',
        start_date      => systimestamp,
        repeat_interval => 'freq=hourly; byminute=42',
        enabled         => TRUE,
        comments        => 'Test Scheduler Start_Date.');
    end;
    /
    
    select systimestamp from dual;
    
    SYSTIMESTAMP
    -------------------------------
    30.03.12 10:42:15,296000 +02:00
    
    select job_name
         , start_date
      from dba_scheduler_jobs
     where job_name like 'TEST_MPR_JOB';
    
    JOB_NAME     START_DATE
    ------------ -------------------------------
    TEST_MPR_JOB 30.03.12 10:42:15,265000 +02:00
    
    -- abgerufen nach dem Lauf
    select log_date
         , req_start_date
         , actual_start_date
      from dba_scheduler_job_run_details
     where job_name = 'TEST_MPR_JOB';
    
    LOG_DATE                          REQ_START_DATE                    ACTUAL_START_DATE
    --------------------------------- --------------------------------- -------------------------------
    30.03.12 10:42:15,750000 +02:00   30.03.12 10:42:15,300000 +02:00   30.03.12 10:42:15,750000 +02:00
    

    Offenbar wird der systimestamp in diesem Fall als start_date übernommen, wobei die granulareren Zeitangaben - in diesem Fall also die Angaben unterhalb der Minutenangabe - in den Zeitplan eingehen, so dass die Ausführung nicht auf 42:00,000000, sondern auf 42:15,300000 festgelegt wird (REQ_START_DATE). Der tatsächliche Start (ACTUAL_START_DATE) verzögerte sich im gegebenen Fall minimal und entspricht dann auch dem LOG_DATE.

    Interessant ist allerdings, was passiert, wenn man auf die start_date-Angabe komplett verzichtet:

    begin
      dbms_scheduler.create_job (
        job_name        => 'test_mpr_job',
        job_type        => 'plsql_block',
        job_action      => 'begin null; end;',
        repeat_interval => 'freq=hourly; byminute=42',
        enabled         => TRUE,
        comments        => 'Test Scheduler Start_Date.');
    end;
    /
    
    select job_name
         , start_date
      from dba_scheduler_jobs
     where job_name like 'TEST_MPR_JOB'
    ;
    
    JOB_NAME     START_DATE
    ------------ --------------------------------------
    TEST_MPR_JOB 30.03.12 10:55:12,603528 EUROPE/VIENNA
    

    Wien kam dabei etwas unerwartet, aber die Dokumentation hat dazu folgende Erklärung:
    When start_date is NULL, the Scheduler determines the time zone for the repeat interval as follows:
    1. It checks whether or not the session time zone is a region name. The session time zone can be set by either:
    • Issuing an ALTER SESSION statement, for example: SQL> ALTER SESSION SET time_zone = 'Asia/Shanghai';
    • Setting the ORA_SDTZ environment variable.
    2. If the session time zone is an absolute offset instead of a region name, the Scheduler uses the value of the DEFAULT_TIMEZONE Scheduler attribute. For more information, see the SET_SCHEDULER_ATTRIBUTE Procedure.
    3. If the DEFAULT_TIMEZONE attribute is NULL, the Scheduler uses the time zone of systimestamp when the job or window is enabled.

    Im gegebenen Fall ist Punkt 2 relevant: die DEFAULT_TIMEZONE für den Scheduler wurde explizit gesetzt:

    select dbms_scheduler.stime
      from dual;
    
    STIME
    -----------------------------------------
    30.03.12 11:00:07,645730000 EUROPE/VIENNA
    

    Wahrscheinlich wurden also die Jobs, die die Sommerzeit korrekt umsetzten, ohne start_date-Angabe angelegt und konnten deshalb den Scheduler-Default verwenden. Alternativ könnte man das start_date auch explizit auf eine Angabe mit Zeitzone setzen:

    begin
      dbms_scheduler.create_job (
        job_name        => 'test_mpr_job',
        job_type        => 'plsql_block',
        job_action      => 'begin null; end;',
        start_date      => systimestamp at time zone 'EUROPE/BERLIN',
        repeat_interval => 'freq=hourly; byminute=55',
        enabled         => TRUE,
        comments        => 'Test Scheduler Start_Date.');   
    end;
    /    
    

    Die einfachste Möglichkeit zur Korrektur der fehlerhaften Angaben war dann eine explizite Änderung der start_date-Angabe mit Hilfe der set_attribute-Prozedur:

    begin
        DBMS_SCHEDULER.SET_ATTRIBUTE(
            name => 'test_mpr_job'
          , attribute => 'start_date'
          , value => '28.03.12 11:00:00,000000 EUROPE/BERLIN');
    

    Freitag, März 09, 2012

    Interessante Kommentare im PL/SQL-Core

    Morten Braten hat ein paar nette Kuriositäten in den Kommentaren des STANDARD-Packages gefunden, die man sich über ALL_SOURCES auch selbst anschauen kann. Mein persönlicher Favorit ist: "Perhaps this can be done more intelligently in the future."

    Da also auch, denke ich.

    Freitag, März 02, 2012

    SYS_CONTEXT

    Bei Uwe Hesse findet man ein nettes whoami-Script, das mit Hilfe von SYS_CONTEXT alle möglichen Informationen zum angemeldeten Benutzer liefert.