Sommerparty 2022

It’s Summer time und der Countdown läuft – am Donnerstag (25.08) findet unser legendäres DBConcepts Sommerfest in der La Crêperie, An der Oberen Alten Donau 6, 1210 Wien, statt.

Wir möchten Sie gerne über die Anreise,- und Parkmöglichkeiten informieren.

Anreise mit den öffentlichen Verkehrsmitteln (Routenplaner).

  • U1 bis zur Station VIC. Anschließend mit Stadtbus 20A Richtung Neue Donau acht Halte bis Sandrockgasse. Der Bus fährt 2-mal die Stunde. Genau Fahrtzeiten können Sie dem Fahrplan Danach ist ein Fußweg von ca. 7 Minuten zu bewältigen.
  • Mit der U6 bis Station Wien Neue Donau (Fahrplan U6) – anschließend ein Fußweg von ca. acht Minuten.
  • S-Bahn-Station Wien Floridsdorf – danach noch ein Fußweg von 7 Minuten

Parkmöglichkeiten für die Anreise mit dem Auto

La Crêperie, An der Oberen Alten Donau 6, 1210 Wien

Ferdinand-Kaufmann-Platz 16 (parkopedia.at). Bitte beachten Sie, dass die Kurzparkzone bis 22 Uhr gilt.

 

Datum: 25. August 2022
Ort: La Crêperie, An der Oberen Alten Donau 6, 1210 Wien
Uhrzeit: ab 16:00 Uhr bis 23:00h

Wir freuen uns auf Sie!!

P.S.: Es erwartet Sie eine Tombola mit einem sensationellen Hauptgewinn. Sowie unser legendärer Pub-Quiz mit großartigen Preisen für die ersten 3 platzierten Teams! Mitmachen ist selbstverständlich kein „Muss“ aber der Spaß ist garantiert!

 

 

Anmeldung zur DBConcepts Sommer-Party 2022

DBConcepts Sommer Party Feiern Sie mit uns den Sommer ganz relaxed an der Alten Donau bei einem fantastischen Grill-Buffet und kühlen Getränken!

Oracle SQL Model Clause Vertiefung

Grundlegender Aufbau

Der grundlegende Aufbau der Model Clause ist bereits in einem anderen Blog-Eintrag beschrieben, den sie HIER finden.

Weitere Steuerungsmöglichkeiten

Im ersten Blog-Artikel wurde nur das main_model beachtet und auch von diesem nur die verpflichtenden Teile. Ergänzen wir das ganze nun um die cell_reference_options.

Die Cell Reference Options definieren wie mit fehlenden und leeren Werten umgegangen wird und wie streng die Eindeutigkeit geprüft wird. Im Detail sieht das wie folgt aus:

IGNORE NAV
Wenn diese Anweisung inkludiert wird, dann werden folgende Default Werte bei NULL-Werten oder fehlenden Werten zurückgegeben

  • 0 für numerische Spalten
  • 01.2000 für Datumsspalten
  • Ein leerer String für Textspalten
  • NULL für alle anderen Datentypen

KEEP NAV (Default)
Wenn diese Anweisung inkludiert wird, wird immer NULL zurückgegeben, wenn ein Wert fehlt oder NULL ist

UNIQUE DIMENSION (Default)
Wenn diese Anweisung inkludiert ist, muss die Kombination der Spalten der PARTITION BY und der DIMENSION Spalten eine Zeile eindeutig identifizieren (die Spalten müssten also einen Unique Key definieren können)

UNIQUE SINGLE REFERENCEMit dieser Anweisung werden lediglich Referenzen auf eine einzelne Zelle auf der rechten Seite auf Uniqueness geprüft, nicht das gesamte Set

NAV Anweisung

Wenn Werte nicht gefunden werden, definiert diese Anweisung wie damit umgegangen wird. Als Beispiel auf Basis der vorhandenen Testdaten sollen die folgenden beiden Statements zeigen wie die Auswirkungen sind:

select schueler, note, schulstufe
  from schulnoten
 where schuelernummer = 1
   and fach = 'Deutsch' MODEL DIMENSION BY(schueler, schulstufe)
 MEASURES(note) keep nav(note [ 'Anton Anger',
           5 ] = note [ schueler = 'Anton Anger',
           schulstufe = 4 ]);

In diesem Fall mit KEEP NAV werden nicht vorhandene Werte mit NULL ausgegeben. Die Zeile mit Schulstufe 5 hat also in der Notenspalte einen NULL Wert stehen.

select schueler, note, schulstufe
  from schulnoten
 where schuelernummer = 1
   and fach = 'Deutsch' MODEL DIMENSION BY(schueler, schulstufe)
 MEASURES(note) ignore nav(note [ 'Anton Anger',
           5 ] = note [ schueler = 'Anton Anger',
           schulstufe = 4 ]);

Wenn nun IGNORE NAV verwendet wird, dann wird der Wert 0 eingetragen.

UNIQUE Anweisung

Die Unique Anweisung ist sehr simpel umzusetzen. Auf Basis der schon im ersten Blogeintrag verwendeten Testdaten würde folgendes Statement fehlschlagen:

  from schulnoten
 where schuelernummer = 1
   and fach = 'Deutsch' MODEL DIMENSION BY(schueler) MEASURES(note)
 unique dimension (note [ schueler = 'Anton Anger'
                 ] = round(avg(note) [ schueler = 'Anton Anger' ], 0));

Der Grund dafür ist simpel: Die Spalte SCHUELER definiert keine eindeutige Zuweisung. Um das Statement valid zu machen, müsste man das Jahr oder die Schulstufe ergänzen, das würde dann wie folgt aussehen:

select schueler, note
  from schulnoten
 where schuelernummer = 1
   and fach = 'Deutsch' MODEL DIMENSION BY(schueler, schulstufe)
 MEASURES(note) unique
 dimension(note [ schueler = 'Anton Angera',
                 schulstufe = 4
                 ] = round(avg(note) [ schueler = 'Anton Angera',
                           schulstufe between 1 and 3 ],

Oder alternativ die UNIQUE Anweisung ändern, was dann so aussehen würde:

select schueler, note
  from schulnoten
 where schuelernummer = 1
   and fach = 'Deutsch' MODEL DIMENSION BY(schueler) MEASURES(note)
 unique single
 reference(note [ schueler = 'Anton Anger'
                 ] = round(avg(note) [ schueler = 'Anton Angera' ], 0));

 

APEX: Calendar Custom Classes

APEX: Calendar Custom Classes

Der native Kalender in APEX bietet bereits eine Vielzahl an möglichen Klassen um Termine passend anzuzeigen. Die Inline-Hilfe in APEX bietet folgende Liste von 14 Klassen an

 

Wie der letzte Satz andeutet, ist auch das Hinzufügen von eigenen zusätzlichen Klassen möglich.
Da die Dokumentation hier keine klaren Hinweise bietet, soll hier kurz aufgelistet werden, wie diese Klassen anzulegen sind.

Die beste Orientierung bietet hier eine der Klassen, welche von APEX bereitgestellt werden:
.fc .fc-event.apex-cal-black {
   background-color:#303030;
   border-color:#303030;
   color:#fff
}

Der Klassenname, welche in APEX in einer Spalte übergeben wird muss also in auf folgende Weise in einem CSS-File oder Inline auf der Seite eingebaut werden:

.fc .fc-event.[CUSTOM-CLASS_NAME] {
   CSS-Eigenschaften
}

Bei den CSS-Eigenschaften kann man sich ebenfalls an den Default-Klassen orientieren, die drei relevantesten Eigenschaften sind offensichtlicherweise Background, (Foreground-/Font-)Color und Border. Ein paar weitere Ideen, welche anderen CSS-Eigenschaften hier noch sinnvoll einsetzbar sind wären z.B.:

  • Alles was die Schrift betrifft wie
    *Font-weight
    *Font-family
    *Font-decoration

 

  •  Weitere Spielereien wie
    *Border-radius
    *Border-style

Als kleine Unterstützung bei der Recherche für weitere Styling Möglichkeiten:
Die Elemente selbst sind Links (<a>), welche über eine andere Klasse (fc-h-event) als Block-Element dargestellt wird, Klassen welche sich also lediglich auf Inline-Elemente auswirken werden keinerlei Effekt haben.

 

SQL Statements mithilfe von SQL Macros schlanker und dadurch effizienter schreiben

SQL Macros

SQL Macros

Des Öfteren kommt es vor, dass man ein und denselben Ausdruck über mehrere SELECT Befehle oder WHERE Bedingungen benötigt.
Mit den SQL Macros gibt es nun ein Werkzeug, um solche SQLs lesbarer und performanter zu gestalten.

Ein SQL Macro ist eine ausgelagerte Funktion, welche innerhalb eines SQL-Befehls aufgerufen wird. Dieses Konstrukt wird verwendet, um gängige SQL-Ausdrücke auszulagern und wiederzuverwenden. Überall, wo man normalerweise Funktionen aufrufen kann, können SQL Macros verwendet werden. In Oracle 20c nur als preview in der Cloud vorhanden, sind SQL Macros ab Oracle 21c fixer Bestandteil von Oracle Datenbanken, jedoch gibt es schon ab der Version 19.6 SQL TABLE Macros.

Generell gibt es zwei Arten von SQL Macros:

SCALAR: Liefert einen Ausdruck und kann im SELECT, WHERE, HAVING, GROUP BY und ORDER BY Teil des Befehls verwendet werden.

TABLE: Liefert einen Ausdruck und darf ausschließlich im FROM Teil des Befehls verwendet werden.

Aufbau:

Wird der Macro Typ nicht angegeben, wird das Macro defaultmäßig als TABLE Macro erstellt. Da in der Oracle Version 19.6 nur TABLE Macros zur Verfügung stehen, muss der Typ nicht angegeben werden.

Das SQL Macro wird nicht erst während der runtime ausgeführt, sondern bereits während dem parsen der Abfrage und liefert im Anschluss einen String (VARCHAR2, CHAR, CLOB) in die Hauptabfrage zurück. Dem Optimizer stehen daher schon alle nötigen Informationen zur Optimierung des SQLs zur Verfügung. Da das SQL Macro nur einmal während der parse time ausgeführt wird, entfällt daher auch der context switch zwischen SQL und PL/SQL engine. Des Weiteren können Parameter einem Macro mitübergeben werden. Dies ist besonders bei VIEWs interessant, denn bis dato konnte man VIEWs nicht parametrisieren.

Beispiele

Beispiel (Scalar SQL Macro): Wir wollen aus der Tabelle „emp“ alle Mitarbeiter ausgeben, welche schon mehr als 38 Jahre im Unternehmen angestellt sind.

Der Ausdruck „FLOOR(MONTHS_BETWEEN(TRUNC(SYSDATE), t1.hiredate)/12)” wird in diesem Statement zwei Mal verwendet, diesen können wir also auch in ein SQL Macro auslagern.

Die Funktion „job_duration“ wurde als SCALAR SQL Macro angelegt und ist nun verwendbar.



Beispiel (TABLE SQL Macro): Wir wollen aus der Tabelle „emp“ alle Mitarbeiter mit dem Jobtitel „ANALYST“ ausgeben.


Wir können den Ausdruck in der FROM Klausel auch als Macro auslagern.


Die Funktion „analyst_emp“ wurde als TABLE SQL Macro angelegt und ist nun verwendbar.

Folgendes Macro liefert zwar beim Kompilieren keinen Fehler, jedoch beim Aufruf im SQL Statement:

Fehler Nummer 1: Zwei Ergebnisspalten in einem Macro. Dies ist nicht möglich, da ein Macro immer nur eine Ergebnisspalte zurückliefert.

Fehler Nummer 2: Spalten Alias in einem Macro. Da Macros unter Anderem auch in der WHERE Klausel verwendet werden können und diese mit Spalten Aliase nicht arbeiten können, wird hier ein weiterer Fehler erzeugt.

Parameter

Um mehr Flexibilität in SQL Macros zu erlangen, können Parameter verwendet werden. Beispiel: Wir wollen anhand eines beliebig gewählten Jahres wissen, welche Mitarbeiter schon länger als 38 Jahre im Unternehmen tätig sind.

Nun haben wir das SQL Macro erstellt und können es nun in unserer Abfrage verwenden.

Data Dictionary

Da SQL Macros eigene Datenbankobjekte sind, sind diese auch im Data Dictionary ersichtlich.



 

CBQT-ORE and its FIRST_ROWS optimization inability

CBQT-ORE and its FIRST_ROWS optimization inability

Terminology:

CBQT: cost based query transformation
ORE: or expansion

Some days ago, I took a look on a customer’s performance issue which was introduced after their upgrade from 12.1 to 19c. Quite fast we could narrow it down to a new optimizer feature „cost based or expansion“.

To get a basic understanding of the feature, I’ll recommend reading Optimizer Transformations: OR Expansion by Oracle’s CBO PM Nigel Bayliss.

As a quick fix we simply disabled the feature by setting „_optimizer_cbqt_or_expansion“ = off and the performance went back to good again.

Afterwards I wanted to understand the root cause and built a model to reproduce the problem.

rem ######################################
rem # set environment                    #
rem ######################################
alter session set statistics_level=ALL;

rem ######################################
rem # prophylactic cleanup               #
rem ######################################
drop table asc_t2;
drop table asc_t1;
drop table asc_t3;

rem ######################################
rem # create testdata                    #
rem ######################################

--will hold 1M rows with a unique ID
create table asc_t1
as
with gen as
(
   select rownum dummy from dual connect by level <= 1e4
)
select rownum id
  from gen, gen
 where rownum <= 1e6;

--will hold 1M rows with 500 distinct values of T1_ID (20000 records per value)
create table asc_t2
as
select id, mod(id, 500) t1_id, lpad('*', 250, '*') pad
  from asc_t1;

--will hold 1M rows with 500 distinct values of T1_ID (20000 records per value)
create table asc_t3
as
select id, t1_id, pad
  from asc_t2;

--indexes
create unique index asc_t1_uk on asc_t1 (id);
create unique index asc_t2_uk on asc_t2(id);
create index asc_t2_i1 on asc_t2(t1_id);
create unique index asc_t3_uk on asc_t3(id);
create index asc_t3_i1 on asc_t3(t1_id);

And this is the query to be examined:

select t1.*
  from asc_t1 t1
 where id between 1 and 10
   and exists (select 1
                 from asc_t2 t2, asc_t3 t3
                where t2.id = t3.id
                  and (t2.t1_id = t1.id or t3.t1_id = t1.id)
              );

With no hints or further parameters set, this is the plan including some rowsource execution statistics pulled from memory after its execution:
So at first we can see that ORE was used and the query was split into two disjunct union all branches. What’s really interesting for me at a first sight is the presence of a blocking operation in the VW_ORE block (HASH JOIN at ID 4), despite the fact it is called in an exists clause and therefore has the ability to leave after the first row is found.

So for each row we got from ASC_T1 an in-memory hash table was build after scanning the appropriate index on T2 and visiting the table block (IDs 6 and 5).  In total 20.000 rows on 20.078 buffers were read on these operations. After that index ASC_T3_UK was probed against that hash table and here we see the effects of “exists” very cleary:  despite the index contains 1 million entries just 55 rows over all 10 calls needed to be read to find a first match and therefore be able to quit, because the exists clause was fulfilled. The second union-all branch wasn’t called at all.

Let’s look at two more examples.

First: forbid CBQT-ORE from kicking in.

select t1.*
  from asc_t1 t1where id between 1 and 10 
 and exists (select /*+ no_or_expand */1
                          from asc_t2 t2, asc_t3 t3
                        where t2.id = t3.id
                              and (t2.t1_id = t1.id or t3.t1_id = t1.id)
                            );

And its rowsource execution statistics:
It has got a much lesser cost than the ORE plan and much less buffers were visited to execute the query. It gives also the impression that the CBO is now aware that it has to deal with an “exists” clause here, because that part of the plan was optimized to find a first matching row very quickly. E.g low cost for the FTS on ASC_T2 or the absence of blocking operations are indicators for this strategy.

Second: Switch back to LORE (Legacy OR Expansion)

select /*+ opt_param('_optimizer_cbqt_or_expansion', 'off') */ t1.* 
  from asc_t1 t1
 where id between 1 and 10
    and exists (select 1
                             from asc_t2 t2, asc_t3 t3
                          where t2.id = t3.id
                                and (t2.t1_id = t1.id or t3.t1_id = t1.id)
                            );

Again rowsource execution statistics:
This one now shows the lowest cost and least buffers visited overall. It also seems to be aware of the exists clause and adapts a first_rows strategy in the relevant parts of the execution plan! This is basically what I would have expected from the CBQT-ORE.

So we now have a theory that CBQT-ORE loses track that its query block is called in an “exists” clause and provides an execution plan as if it was a standalone query, where it would need to fetch all the rows.

There’s some more evidence to this theory.

1.) The transformed query from the VW_ORE query block shows the same cost and execution plan when it is costed as a standalone query. The correlated value from the outer rowsource was replaced with a bind variable in the following example:

select 0
  from (
             select 1
                 from asc_t3 t3, asc_t2 t2
              where t2.id = t3.id and t2.t1_id = :b1
            union all
           select 1
               from asc_t3 t3, asc_t2 t2
            where t2.id = t3.id      
                 and t3.t1_id = :b1
                 and lnnvl (t2.t1_id = :b1)               );

generates this plan, which matches the “VW_ORE_82971ECB” from our first query.



2.) The 10053 traces for both the disabled CBQT-ORE and the old-style OR-Expansion (LORE) show lots of references that a first_rows optimization approach was chosen for the query blocks in question.

Like:

On the other hand when looking at the 10053 trace of the first query, interestingly before the ORE checks kicked in, the same plan was already found which was used in query 2, where I explicitly disabled the transformation.

Final cost for query block SEL$2 (#2) – First K Rows Plan:
  Best join order: 1
  Cost: 506.142990  Degree: 1  Card: 2.000000  Bytes: 71964.000000
  Resc: 506.142990  Resc_io: 506.000000  Resc_cpu: 4233492
  Resp: 506.142990  Resp_io: 506.000000  Resc_cpu: 4233492

Later in the same tracefile, the ORE transformation and costing was performed and produced a final cost (8591) which was (way) higher than the formerly calculated plan with a final cost of 506. But however at the end it was not picked and the CBO stayed with the cheapest ORE-plan.

Summary:
CBQT-ORE does not seem to be aware when its query block resides in an “exists” clause and therefore doesn’t optimize for first rows access patterns. The query block gets optimized as if all rows would be needed to fetch. Additionally in certain scenarios it seems to “forget” if cheaper plans were found during the whole optimization process. If this effects you in a negative way, as a first action the cost based transformation can be turned off and the legacy version used instead. Additionally an SR was raised to tackle this issue.

Further Reading:
Excellent articles on the topic of CBQT Or-Expansion can also be found on the blogs of Patrick Joliffe, Mohamed Houri and Nenad Noveljic

Update 1:
After playing with the same testcase in my 21c lab I noticed the that the problem went away. After quick look into v$system_fix_control I found bug 28414968 “expansion with some constant branches;fkr1 in (NOT)EXISTS subque” which is first available in 19.11 and has to be activated proactively. That plan now gets produced in in 21c and after setting alter session set „_fix_control“=’28414968:3′; in >=19.11
10 buffers less visited compared to the LORE plan, as ID 8 was superfluous in the LORE plan.

Funnily, if I code the union-all instead of the or-predicate myself, I still get the old plan. But that’s one topic for another post I guess.

select t1.id id
  from demo.asc_t1 t1
 where t1.id >= 1
   and t1.id <= 10
   and exists (select 0
                            from demo.asc_t3 t3, demo.asc_t2 t2
                         where t2.id = t3.id
                               and t2.t1_id = t1.id 
                         union all
                         select 1
                             from demo.asc_t3 t3, demo.asc_t2 t2
                          where t2.id = t3.id
                                and t3.t1_id = t1.id
                                and lnnvl (t2.t1_id = t1.id)
                            );

Sommerparty Banner

Das war die DBConcepts Sommer-Party 2021

Zum vierten Mal fand heuer wieder unsere legendäre DBConcepts Sommerparty in der La Creperie an der Alten Donau statt. Nach einem langen Jahr der Zwangspausierung haben wir eine Rekordzahl an Anmeldungen für unsere Sommer Party entgegengenommen und über 70 TeilnehmerInnen begrüßen können.

Doch diese Party war nicht nur besonders da es unsere erste nach dem Corona Jahr sein sollte. Gemeinsam mit unseren Kunden, Partnern und Kollegen haben wir das 20jährige Jubiläum der DBConcepts gefeiert, zwar nachgefeiert.. aber zumindest gebührend.

Gemeinsam haben wir einen entspannten Abend mit einigen Highlights verbracht. Als Willkommensgeschenk und gab es heuer nicht nur unsere sehr beliebten Sonnenbrillen, sondern auch ein großartiges Strandtuch mit Unterstützung der Arrow.

Nach Meteorologischer Ansage sollte unsere Tretbootchallenge auch dieses Jahr eher ins Wasser fallen, doch dieses Mal war auch für eine Alternative gesorgt – das erste DBConcepts PubQuiz.

Insgesamt 13 Teams haben es sich zur Aufgabe gemacht, die fünf verschiedenen Themenblöcke in kürzester Zeit zu beantworten. Die Themen rangierten von DBConcepts Wissen über Popkultur bis hin zu geografischen Kenntnissen. Die glorreichen Sieger durften sich über tolle Preise freuen!

Sehr besonders freut uns das begeisterte Feedback aller Gäste, die uns zur gelungen Sommer-Party gratuliert haben.

Nach der Sommer-Party ist immer vor der Sommer-Party.

In diesem Sinne freuen wir uns schon auf nächstes Jahr!

Oracle Lizenzierung News

Aktuelle Neuigkeiten in der Oracle Lizenzierung

Es gibt gute Neuigkeiten im Bereich Migration von Oracle Lizenzen!

Laut aktuellen Informationen von Oracle ist es seit 02. Juli 2021 nun nicht mehr notwendig bestehende, alte, Oracle Standard Edition (SE) bzw. Standard Edition One (SEO) Lizenzen (bis 12.1.0.1) bei Oracle auf aktuelle Standard Edition 2 (SE2) (ab 12.1.0.2) Lizenzen migrieren zu lassen. Damit entfällt für SEO Lizenzen auch die Supportpreiserhöhung und es wird somit einfacher und kostengünstiger.

Von nun an reicht es, wenn Sie beim Software Download aus der Oracle Delivery Cloud den aktuellen Lizenzbedingungen der SE2 einfach zustimmen.
Wollen Sie dennoch Ihre bestehenden und unter aufrechtem Support stehenden Oracle SE/SEO Lizenzen auf Oracle SE2 Lizenzen migrieren lassen, so können Sie das weiterhin beantragen. Auch in diesem Fall wird es für alle SE Editionen eine 1:1 Migration ohne Migrationskosten und Supportpreiserhöhung.

Bei Migrationen, die Änderungen der generellen Metriken oder ein Ändern der Lizenzstufe benötigen, wenden Sie sich bitte weiterhin an unsere Lizenzexperten.

Zusammenfassend kann man sagen, dass diese Nachrichten für Anwender sehr erfreulich sind.

Eine aktive Migration von SE/SEO auf SE2 ist nun nicht mehr erforderlich.

Weiters entfällt die, vorher obligatorische, Supportpreiserhöhung für SEO Lizenzen im Rahmen der Migration auf SE2.

Falls Sie noch Fragen zu Ihrer Oracle Lizenz oder einer Migration haben, kontaktieren Sie uns gerne direkt!

Sommerparty Banner

Anmeldung zur DBConcepts Sommer-Party 2021

Endlich ist es wieder soweit! Nach langem Warten, werden wir wieder unser DBConcepts Sommerfest veranstalten. Feiern Sie mit uns den Sommer ganz relaxed an der Alten Donau bei einem fantastischen Grill-Buffet und kühlen Getränken! Für besonders gute Laune sorgt auch heuer wieder die beliebte DBConcepts Tretboot-Challenge und als zusätzliches Schmankerl werden wir am Abend ein […]