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:
- JSON
- Polymorphe Tabellenfunktionen (PTF)
Group By ()
mit leerem InputListagg
ohneWithin Group (Order by ...)
- Jenseits des SQL Standards
- 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 Moduslax
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 vonsize()
in JSON Path Filtern zu geben – zumindest habe ich nicht das erwartete Ergebnis erhalten.0type()
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
undc2
mit dem jeweils entsprechenden Typen.Nach dem Anlegen können Tabellenfunktionen in der
from
undjoin
-Klausel verwendet werden.4Prominente Beispiele für Tabellenfunktionen sind:
Oracle’s
dbms_xplan.display_cursor
(und Artverwandte)PostgreSQL’s
generate_series
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.
- Proprietäre Syntax
- Funktionen die
setof record
liefern, können dynamische Tabellen liefern
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 biete SQL Schulungen, Optimierung und Beratung an. Auch der Kauf meines Buches „SQL Performance Explained“ (ab €9,95) unterstützt meine Arbeit an dieser Webseite.
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.
- Liefert bei leerer Eingabe keine Zeile
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 Funktionapprox_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.