Neues in der Oracle-Datenbank 18c


Im Februar 2018 wurde Release 18c der Oracle-Datenbank für Nutzer der Oracle Cloud und eines Engineered-Systems freigegeben. Es dauerte weitere fünf Monate – bis Juli – bevor es einen Download zur Installation auf eigenen Servern gab. Nach weiteren drei Monaten – im Oktober – wurde dann auch die kostenlose Express Edition (XE) auf Version 18c gehoben. Ich glaube man kann sagen, dass die Oracle-Datenbank 18c jetzt vollständig erschienen ist. Für mich der Anlass, einen Blick aus der Perspektive des SQL-Standards darauf zu werfen.

Beachte, dass die Oracle-Datenbank 18c nur eine “kleine” Version ist – obwohl die vorherige Version 12.2 genannt wurde. Oracle hat sich lediglich dazu entschieden, fortan die letzten beiden Stellen der Jahreszahl als Versionsnummer zu verwenden.

Inhalt:

  1. JSON
  2. Polymorphe Tabellenfunktionen (PTF)
  3. Group By () mit leerem Input
  4. Listagg ohne Within Group (Order by ...)
  5. Jenseits des SQL Standards
  6. Als Nächstes auf Modern-SQL.com

JSON

Wenn du mit dem Standard-SQL JSON-Support nicht vertraut bist, findest du in meinem Artikel Was ist neu in SQL:2016 einen Überblick.

JSON Path

Der SQL-Standard verwendet die sogenannte SQL/JSON Path Sprache, um Elemente eines JSON-Dokumentes zu selektieren – ähnlich wie XPath für XML oder CSS Selektoren für HTML.

JSON Path hat zwei Modi: den voreingestellten Modus lax und strict. Zuvor wurde das Schlüsselwort strict in JSON-Path-Ausdrücken zwar akzeptiert, aber ignoriert. Bei Version 18c erhält man nun eine Fehlermeldung.

Eine ähnliche Änderung gab es bei den sogenannten Item-Methoden – das sind Methoden, die auf JSON-Elemente angewendet werden. Die meisten Item-Methoden haben zuvor nicht korrekt funktioniert – sie haben scheinbar nur den Operanden (das JSON-Element) geliefert. In 18c liefern die folgenden Funktionen einen Fehler: keyvalue(), double(), ceiling(), floor() und abs(). Einige davon scheinen jedoch noch in der Dokumentation auf – offenbar hat man einen Fehler durch einen anderen ersetzt.

Zwei andere Item-Methoden funktionieren ab 18c – zumindest teilweise:

size()

Liefert die Länge eines JSON-Arrays oder 1 bei anderen Datentypen.

Beachte, dass size() im Modus lax schwierig zu benutzen ist, weil in diesem Modus Arrays in manchen Fällen automatisch ausgepackt werden („unwrap“). Weiters scheint es in 18c einen Fehler bei der Verwendung von size() in JSON Path Filtern zu geben – zumindest habe ich nicht das erwartete Ergebnis erhalten.0

type()

Liefert einen String, mit dem Namen des JSON-Typen: null, number, string, boolean, date, time[stamp] [with|without] timezone, array, object.

Die Oracle-Datenbank bietet auch proprietäre Item-Methoden an (siehe “SQL/JSON Path Expression Item Methods” in der Dokumentation).

Json_array[agg] mit Absent On Null als Voreinstellung

Der SQL-Standard definiert die Voreinstellung für den Umgang mit Null-Werten bei json_array und json_arrayagg als absent on null.1 Bei json_object und json_objectagg ist die Voreinstellung jedoch null on null.2 Frühere Versionen der Oracle-Datenbank haben auch bei den Array-Funktionen null on null als Voreinstellung verwendet. Version 18c verhält sich nun entsprechend dem Standard. Braucht man das alte Verhalten, muss man null on null explizit angeben.

Jenseits von Standard SQL/JSON

Die meisten JSON-Funktionen die mit Version 18c neu eingeführt wurden, sind proprietär. Tim Hall hat einige Artikel dazu veröffentlicht (Englisch).

Eine dieser Funktionen wäre es vielleicht sogar Wert, in den Standard aufgenommen zu werden:3 json_equal. Sie führt einen semantischen Vergleich zweier JSON-Dokumente durch – Formatierung und irrelevante Reihenfolge von Objekt-Attributen werden dabei ignoriert.

Polymorphe Tabellenfunktionen (PTF)

Dazu will ich aus meinem Artikel über die neuen Funktionen in SQL:2016 zitieren:

SQL Tabellenfunktionen – eingeführt mit SQL:2003 – sind Funktionen, die eine Tabelle als Ergebnis liefern. Tabellenfunktionen müssen die Namen und Typen der Ergebnisspalten – den sogenannten Zeilentyp – in der Deklaration festlegen:

CREATE FUNCTION <name> (<parameters>)
       RETURNS TABLE (c1 NUMERIC(10,2), c2 TIMESTAMP)
   ...

Diese Funktion liefert also eine Tabelle mit zwei Spalten: c1 und c2 mit dem jeweils entsprechenden Typen.

Nach dem Anlegen können Tabellenfunktionen in der from und join-Klausel verwendet werden.4

Prominente Beispiele für Tabellenfunktionen sind:

SQL:2016 führt polymorphe Tabellenfunktionen (PTF) ein: Diese müssen den Ergebnistyp nicht vorab festlegen. Stattdessen können sie eine describe component procedure bereitstellen, die den Ergebnistyp zur Laufzeit festlegt. Weder der Autor der PTF, noch der Nutzer müssen die Spalten Typen oder Namen vorweg festlegen.

Oracle 18c führt polymorphe Tabellenfunktionen ein, verwendet aber eine proprietäre Syntax.5 Ich habe die Unterschiede nicht im Detail analysiert, aber es scheint, als wären die Konzepte und die Funktionalität der Oracle-Lösung und Standard-PTFs gleichwertig.

Lauffähige Beispiele zeigt Tim Hall. Eine interessante Anwendung von Ihm ist, wie man fast alle Spalten eine Tabelle selektiert – im Sinne von select * except (column). Eine ähnliche Funktionalität in der Standard-Syntax findet man übrigens in Klausel 12.2 des technischen Reports „Polymorphic table functions in SQL“ von ISO.

Group By () mit leerem Input

Meine Konformitätstests haben eine Verbesserung gefunden, zu der ich in der Dokumentation nichts gefunden habe. Eine explizite, globale Gruppierung – group by () – liefert in Version 18c selbst dann eine Zeile, wenn die gruppierte Tabelle leer ist.

Lass mich das Schritt-für-Schritt erklären.

Du hast in deiner Laufbahn bestimmt schon Abfragen wie diese verwendet:

SELECT COUNT(*)
  FROM tbl

Es ist eine sehr einfache Abfrage zum Zählen der Zeilen in einer Tabelle.

Nur eine Frage dazu: Wenn die Tabelle leer ist, warum liefert die Abfrage trotzdem eine Zeile? Oder anders gefragt: Wie kann eine Abfrage auf eine leere Tabelle jemals eine Zeile liefern?

Versuch es einfach: Zähle die Zeilen einer leeren Tabelle und du wirst sehen, dass du eine Zeile als Ergebnis erhälst, die besagt, dass die Tabelle keine (0) Zeilen hat.

Hinweis in eigener Sache

Ich lebe von SQL-Schulungen, anderen SQL-Dienstleistungen und dem Verkauf meines Buches. Mehr dazu auf winand.at.

Der Grund dafür liegt darin, wie der SQL-Standard die globale Gruppierung definiert – das ist eine Gruppierung ohne Gruppenschlüssel. In diesem Fall werden alle Zeilen in eine gemeinsame Gruppe gelegt. Im weiteren Verlauf wird – wie bei jeder group by-Operation – diese Gruppe dann in eine Zeile umgewandelt. Das geschieht völlig unabhängig davon, wie viele Zeilen in dieser Gruppe sind. Selbst wenn die Gruppe leer ist, wird sie am Schluss in eine Zeile umgewandelt. Daher kann man die Zeilen in einer leeren Tabelle zählen und tatsächlich 0 als Ergebnis erhalten.

SQL bietet zwei Möglichkeiten die globale Gruppierung zu aktivieren: implizit und explizit. Die implizite globale Gruppierung geschieht, wenn eine Gruppierung notwendig ist – weil man Aggregatfunktionen oder die Having-Klausel verwendet – aber keine explizite Group By-Klausel angegeben hat. Die obere Abfrage verlässt sich auf diese implizite globale Gruppierung.

Die explizite Syntax für die globale Gruppierung ist die leere Klammer in der group by-Klausel:6

SELECT COUNT(*)
  FROM tbl
 GROUP BY ()

Diese Möglichkeit wurde mit SQL:1999 zusammen mit Grouping Sets eingeführt. Seit dem sind die beiden oberen Abfragen gleichwertig – zumindest in Standard SQL.7

Bei der Oracle-Datenbank war das jedoch anders. Lässt man die zweite Abfrage in einer Oracle-Datenbank bis zu Version 12c laufen, erhält man ein leeres Ergebnis.

SQL> SELECT COUNT(*)
2  FROM tbl
3 GROUP BY ();

no rows selected

SQL>

Und genau das hat sich mit Version 18c geändert.

Die praktische Relevanz dieser Konformitätsverbesserung ist nicht sehr groß. Dennoch, es zeigt, dass Oracle willens ist, sich dem Standard anzunähern. Selbst dann, wenn andere Produkte dasselbe „falsche“ Verhalten haben (Microsoft SQL Server). Kein gigantischer Sprung, aber ein netter Schritt, nachdem man es zuvor 17 Jahre anders gemacht hatte.8 Das schürt die Hoffnung, dass Oracle eines Tages vielleicht sogar den einzigartigen null-ist-ein-Leersting-Ansatz ablegt.

Listagg ohne Within Group (Order by ...)

Wie du beim vorherigen Thema gesehen hast, finde ich Kleinigkeiten, die oft nicht in den Releasenotes genannt werden und nicht in der Dokumentation aufscheinen, die interessantesten. Sie offenbaren manchmal woran gerade gearbeitet wird, oder Motive, die nicht offen ausgesprochen werden.

Eine dieser Änderungen betrifft die Listagg-Funktion. Version 18c akzeptiert Listagg nun auch ohne Within Group-Klausel. Obwohl diese Klausel laut Standard notwendig ist, von Version 12.2 auch verlangt wurde, und selbst in den Syntax-Diagrammen von Version 18c noch obligat ist.

Ein kurzer Blick auf die Support-Matrix zeigt jedoch, dass es unter den getesteten Datenbanken nur eine weitere gibt, die Listagg unterstützt – und das auch ohne Within Group-Klausel. Diese Änderung wurde also vermutlich zur Verbesserung der Kompatibilität mit dieser Datenbank durchgeführt.

Jenseits des SQL Standards

Natürlich gibt es in Version 18c auch zahlreiche proprietäre Erweiterungen. Die offizielle Liste findet sich im Oracle Database Release 18c New Features-Dokument. Zwei dieser Funktionen möchte ich hier kurz anreißen.

Property Graph Query Language (PGQL)

Das ist ähnlich den Abfragemöglichkeiten, die mit SQL Server 2017 für Graphen eingeführt wurden – jedoch mit einer anderen Syntax. Die Grundidee ist eine Abfragesprache, die Cypher ähnelt, um damit auf Graphen zuzugreifen, die in SQL-Tabellen gespeichert sind. Die Details finden sich in der Dokumentation.

Näherungsweise Top-N-Abfragen

Näherungsweise Funktionen wurden bereits mit der Oracle-Datenbank 12c eingeführt – z. B. approx_count_distinct. Version 18c führt nun Funktionen ein, mit denen man Top-N-Abfragen – Abfragen nach den ersten N Zeilen – näherungsweise ausführen kann. Umgesetzt wird das mit der neuen Funktion approx_rank. Mehr darüber in der Dokumentation.

Als Nächstes auf Modern-SQL.com

Die nächsten Artikel über SQLite 3.25 (Window-Funktionen!) und PostgreSQL 11 (mehr Window-Funktionen!) sind bereits in Arbeit. Folge modern-sql.com via Twitter, E-Mail oder RSS.

Über den Autor

Foto von Markus Winand

Markus Winand lehrt effizientes SQL – inhouse und online. Er minimiert die Entwicklungszeit durch modernes SQL und optimiert die Laufzeit durch schlaue Indizierung – dazu hat er auch das Buch SQL Performance Explained veröffentlicht.

Sein Buch bei Amazon kaufen

Titelbild von „SQL Performance Explained“: Eichhörnchen läuft durchs Grass

Die Essenz: SQL-Tuning auf 200 Seiten

Bei Amazon kaufen
(Taschenbuch)

Taschenbuch und PDF auch auf Markus' Webseite erhältlich.

Sein Training

Markus verwandelt veraltetes SQL-92-Wissen in solides und zeitgemäßes SQL-Know-how

Erfahren Sie mehr»

Fußnoten

0

Z. B. is 'lax $ ? (@.b.size() == 2)')' für diese JSON-Fragment false '{"b": [1,2]}'. Beachte, dass auto-unwrap für size() und type() explizit ausgeschlossen ist (SQL-2:2016, 9.39 GR 11gii6A).

1

SQL-2:2016 6.33 SR 6a und SQL-2:2016 10.11 SR 5a

2

SQL-2:2016 6.33 SR 5a und SQL-2:2016 10.11 SR 4a

3

Eventuell mit einer anderen Syntax – die Funktionalität wäre aber sehr nützlich.

4

Eine syntaktische Ergänzung ist nötig: table(). Lateral wird ebenfalls oft im Zusammenspiel mit Tabellenfunktionen benutzt.

6

Das schließt die leere Klammer in Grouping Set-Klauseln mit ein.

7

Tatsächlich implementiert der Standard die implizite globale Gruppierung durch eine syntaktische Transformation: Ist eine Gruppierung nötig aber keine group by-Klausel vorhanden, wird group by () angenommen (SQL:2016-2: §7.16 SR6 für Aggregatfunktionen; SQL:2016-2: §7.14 SR1 für having).

8

Grouping sets wurden mit Oracle 9iR1 im Jahr 2001 eingeführt.

„modern SQL“ von Markus Winand ist unter einer Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License lizenziert.
Impressum | Kontakt | KEINE GEWÄHR | Handelsmarken | Datenschutz und DSGVO | CC-BY-NC-ND 3.0 Lizenz