Aus Linux-Magazin 12/2015

Überraschung in SQL: Moderne Möglichkeiten der Abfragesprache

© sergwsq, 123RF

SQL schafft es derzeit nur noch als Antithese in die Medien: NoSQL ist das Hype-Thema. Tatsächlich aber hat die klassische Datenbanksprache viel mehr zu bieten, als den meisten klar ist. Seit SQL:92 kamen mit jeder Version verblüffende neue Funktionen dazu.

Modernes SQL hat mehr drauf, als viele denken. Zum Beispiel ist kaum bekannt, dass SQL längst nicht mehr auf das relationale Datenmodell beschränkt ist, sondern auch mit verschachtelten Objekten und strukturierten Dokumenten umgehen kann. Dafür braucht man kein NoSQL. Dieser Artikel unternimmt einen Streifzug durch die jüngere SQL-Geschichte und zeigt, was die Sprache heute alles leisten kann.

Ausgangspunkt ist der Standard SQL:92, der allgemein bekannt sein dürfte, handelt es sich dabei doch um das klassische, relationale SQL. SQL:92 war bereits die zweite große Version des SQL-Standards, hatte dementsprechend eine gewisse Vollständigkeit erreicht und ist noch heute eine runde Sache – für Anwender, die sich mit dem relationalen Modell begnügen. Dass das relationale Modell nicht für alle Daten ideal ist, war aber schon 1992 bekannt.

Der Urknall – SQL:1999

So kam es 1999 zur dritten großen Version des Standards und damit zum Ende des rein relationalen SQL. Alle Kompasse zeigten damals in Richtung objektorientierter Programmierung. Diesem Trend folgte der Standard mit der objektrelationalen Datenbank. Leider kam er damit aber einige Jahre zu spät. In der Zwischenzeit keimten nämlich objektrelationale Mapper (ORM) auf, die eine Brücke zwischen der objektorientierten Programmierung und dem relationalen Datenmodell bilden sollten.

Neben dem Hauptthema objektrelationale Datenbank bot SQL:1999 auch andere Neuerungen, die sich später besser durchsetzten. Einen dieser Themenbereiche, den der Standard auf breiter Basis unterstützte, beschreibt dieser Beitrag genauer: Schleifen.

Auch wenn SQL:1999 mit mancher Tradition gebrochen hat, so blieb es doch eine deklarative Sprache. Das heißt, dass es per Definition nicht möglich ist, der Datenbank vorzuschreiben, wie sie eine Abfrage auszuführen hat. Solange das Ergebnis stimmt, hat die Datenbank alle Freiheiten. Insofern kann es hier auch keine Schleifen geben, denn die definieren ja gerade die Art und Weise der Lösung. Der Kunstgriff, den SQL an dieser Stelle macht, besteht darin, Konstrukte zu schaffen, die nur als Schleifen ausführbar sind. Diese Konstrukte kann man dann de facto wie Schleifen verwenden, die es in einer deklarativen Sprache eigentlich nicht geben darf.

Als Beispiel soll der PHP- und SQL-Code aus Listing 1 in reines SQL übersetzt werden. Dieses Beispiel lädt zuerst eine Liste von Kategorien in ein PHP-Array und führt dann für jede Kategorie eine weitere Abfrage aus, die die drei jeweils populärsten Produkte jeder Kategorie liefert.

Listing 1

PHP-SQL-Pseudocode

01 $produkt_kategorien = array(SELECT kategorie FROM produkt_kategorien);
02 foreach ($produkt_kategorien as $kategorie) {
03  SELECT *
04  FROM produkte
05  WHERE kategorie = $kategorie
06  ORDER BY popularitaet
07  LIMIT 3;
08 }

Performance-bewusste Anwender könnten an dieser Stelle einwenden, dass es oft zu Problemen führt, wenn man Datenbankabfragen in Schleifen einer imperativen Sprache – hier PHP – platziert. Ein Join ist in der Regel zu bevorzugen, damit die Datenbank alle benötigten Daten auf einmal liefert. In diesem Fall entsteht aber ein Problem: Das Beispiel lässt sich nicht mit einem einfachen Join lösen. Die Beispiele aus Listing 2 zeigen: Egal wie man es dreht und wendet, das Ergebnis sind immer die drei insgesamt populärsten Produkte – nicht die drei populärsten pro Kategorie.

Listing 2

Zwei Versuche mit Joins

01 SELECT *
02  FROM produkt_kategorien k
03  JOIN produkte p ON (p.kategorie = k.kategorie)
04  ORDER BY popularitaet
05  LIMIT 3
06
07 SELECT *
08  FROM produkte
09  WHERE kategorie IN (SELECT kategorie FROM produkt_kategorien)
10  ORDER BY popularitaet
11  LIMIT 3

Das Problem ist, dass »LIMIT« nicht pro Kategorie wirkt. Um das gewünschte Ergebnis zu erhalten, müsste man »LIMIT« in einer Unterabfrage anwenden, die auf eine Kategorie eingeschränkt ist (Listing 3). Dies ist jedoch kein gültiges SQL mehr, da Unterabfragen in der »FROM« -Klausel nicht auf Daten außerhalb dieser Unterabfrage zugreifen können.

Listing 3

Mit Unterabfrage

01 SELECT *
02  FROM produkt_kategorien k
03  CROSS JOIN (SELECT *
04  FROM produkte p
05  WHERE p.kategorie = k.kategorie
06  ORDER BY popularitaet
07  LIMIT 3
08  ) top_produkte

Die »WHERE« -Klausel, die mit »k.kategorie« auf eine Tabelle außerhalb der Unterabfrage zugreift, ist also ungültig. Zumindest ist das in SQL:92 so. SQL:1999 erlaubt solche Zugriffe dennoch, wenn der Anwender der Unterabfrage das neue Schlüsselwort »LATERAL« voranstellt (siehe Listing 4).

Listing 4

LATERAL

01 SELECT *
02  FROM produkt_kategorien k
03  CROSS JOIN LATERAL (SELECT *
04  FROM produkte p
05  WHERE p.kategorie = k.kategorie
06  ORDER BY popularitaet
07  LIMIT 3
08  ) top_produkte

Diese Abfrage entspricht also dem PHP-Code von oben, nur dass die Datenbank selbst die Schleife ausführt und damit die Latenzzeiten zwischen Applikation und Datenbank wegfallen. Abbildung  1 stellt die Analogie zwischen Foreach-Schleife und »LATERAL« nochmals dar.

Abbildung 1: PHP-Pseudocode und SQL-»LATERAL«-Schleifen im Vergleich.

Abbildung 1: PHP-Pseudocode und SQL-»LATERAL«-Schleifen im Vergleich.

Ein weiterer Vorteil, alles in SQL zu erledigen, besteht darin, dass der Anwender das Ergebnis mit SQL weiterbearbeiten kann. So kann er das Gesamtergebnis zum Beispiel mit einer »ORDER BY« -Klausel anders sortieren oder mit »INSERT INTO **… **SELECT **…« direkt in eine Caching-Tabelle schreiben. Der letztere Fall vermeidet den Transport der Daten von der Datenbank in die Applikation und zurück komplett.

Der Vorteil, Ergebnisse mit SQL weiterbearbeiten zu können, gilt natürlich für jede SQL-Abfrage. Generell sollten Nutzer von der Vorstellung abkommen, dass SQL-Datenbanken nur Speicher sind. Oft ist die Datenaufbereitung mit SQL einfacher als mit anderen Programmiersprachen. Das Ergebnis ist in der Regel sogar korrekter und performanter. Dieser Ansatz versagt erst bei massiv parallelen Zugriffen, wie sie bei Google, Facebook & Co. vorkommen. Wer so groß ist, hat aber auch entsprechende Mittel, proprietäre Lösungen zu bauen. Bis dahin ist die Flexibilität von SQL in den meisten Fällen der bessere Weg.

SQL:1999 hat noch ein zweites Konstrukt eingeführt, das ebenfalls wie eine Schleife zu benutzen ist: »WITH RECURSIVE« . Wie es im Detail funktioniert, ist recht komplex und führt hier zu weit, das Grundprinzip verdeutlicht aber die Abbildung  2.

Abbildung 2: »with recursive«-Schleifen in SQL.

Abbildung 2: »with recursive«-Schleifen in SQL.

Die Variante »WITH RECURSIVE« hat drei Vorteile:

  • Es wird von aktuellen Datenbanken besser unterstützt als »LATERAL« – zum Beispiel auch von SQlite (siehe Abbildung  3).
  • Es lassen sich Daten von einer Iteration an die nächste weiterreichen.
  • Es ist möglich, eine dynamische Abbruchbedingung zu formulieren.

Der Nachteil ist, dass der Schleifenkörper (Body) nicht eins zu eins in SQL zu übertragen ist: Er muss mit dem Teil nach »union **all« verschmelzen.

Dennoch gibt es wichtige Anwendungsfälle. Die in Abbildung  2 gezeigte Abfrage ist ein so genannter Zeilengenerator, sie liefert einfach zehn nummerierte Zeilen – besonders praktisch zum Erzeugen von Testdaten. Der wichtigere Anwendungsfall ist jedoch das Durchwandern von Graphen, wie es zum Beispiel vorkommt beim Suchen der kürzesten Verbindung zwischen zwei Personen in einem sozialen Netzwerk.

In schnellen Schritten – SQL:2003

Nachdem das rein relationale Denken mit SQL:1999 überwunden war, dauerte es nur vier Jahre bis zur nächsten großen Überarbeitung des Standards. Der Fokus lag dabei auf zwei Punkten: XML und analytischen Funktionen.

Die XML-Unterstützung ist insofern interessant, da SQL-Datenbanken damit – wie man heute sagen würde – zu vollwertigen Document Stores wurden. Mit SQL:2003 kann der Anwender XML nicht mehr nur als Text abspeichern, sondern als validiertes Dokument, das sich mit SQL und XQuery bearbeiten lässt.

Obwohl mittlerweile einige Datenbanken die XML-Erweiterung unterstützen, konnte sich XML in der Webentwicklung jedoch nicht durchsetzen. Der Konkurrent Json war einfach zu verführerisch. Und so kommt es, dass in den letzten Jahren de facto alle gängigen SQL-Datenbanken ähnliche Funktionen für den Umgang mit Json-Dokumenten eingeführt haben. Das sind jedoch proprietäre Erweiterungen – jede Datenbank bietet andere Funktionen an.

Doch der zweite SQL:2003-Schwerpunkt – analytische Funktionen – hat sich durchgesetzt. Insbesondere die so genannten Window-Funktionen unterstützen heute viele Datenbanken. Sie vereinfachen die Datenaufbereitung wesentlich.

Aggregate ohne <C>GROUP BY<C>

Die grundsätzliche Neuerung von Window-Funktionen besteht darin, dass der Datenbankprogrammierer Aggregat-Funktionen wie »SUM« oder »COUNT« ohne »GROUP BY« verwenden kann. Natürlich muss er weiterhin festlegen, über welche Zeilen das Aggregat zu bilden ist. Wenn er das nicht mit »GROUP BY« macht, weil dadurch Zeilen zusammengefasst werden, kann er nun direkt nach der Aggregat-Funktion selbst die neue »OVER« -Klausel verwenden.

Die folgende Abfrage verdeutlicht die Wirkungsweise: Sie fügt einer Abfrage (»SELECT *« ) eine zusätzliche Spalte hinzu, in der am Ende die Anzahl der Zeilen des Ergebnisses steht:

SELECT *
 , COUNT(*) OVER ()
 FROM [...]

Dabei ist es völlig egal, wie es weitergeht: »JOIN« , »WHERE« , »HAVING« , »GROUP BY« , »ORDER BY« – alles ist möglich. Die Window-Funktion »COUNT(*) OVER ()« hat keine Nebenwirkungen auf die restliche Abfrage. Die leere Klammer in der »OVER« -Klausel bedeutet, dass die »COUNT« -Funktion über alle Zeilen des Ergebnisses angewandt wird.

Die Window-Funktion liest sich als: “Zähle alle Zeilen!” Das Ergebnis dieser Abfrage ist also eine zusätzliche Spalte, die die Anzahl der Zeilen des Ergebnisses beinhaltet. Nochmals in aller Deutlichkeit: Die Zeilenzahl kommt mit jeder Zeile mit, wird also mehrfach geliefert. Das bedeutet aber nicht, dass sie auch mehrfach ermittelt wird!

Natürlich muss man mit Window-Funktionen nicht zwangsläufig das gleiche Ergebnis zigmal liefern. Mit der »OVER« -Klausel lässt sich festlegen, über welche Zeilen die Funktion anzuwenden ist. Das wichtigste Werkzeug zur Eingrenzung der Zeilen ist »PARTITION **BY« :

SELECT *
 , COUNT(*) OVER (PARTITION BY kategorie)
 FROM [...]

Der Ausdruck liest sich nun: “Zähle die Zeilen mit dem gleichen Wert in der Kategorie-Spalte!” »PARTITION BY« grenzt die Zeilen also so ein, wie es auch »GROUP BY« macht. Nur dass diese Form die Zeilen eben nicht zusammenfasst, wie es »GROUP BY« tut, sondern lediglich festlegt, über welche Zeilen eine Window-Funktion anzuwenden ist.

Wenn es mehrere Zeilen mit derselben Kategorie gibt, erhält man für diese Zeilen jeweils das gleiche Ergebnis. Es sei nochmals darauf hingewiesen, dass eine Window-Funktion keine Nebenwirkungen auf das restliche Resultat der Abfrage hat. Das heißt insbesondere auch, dass in einer Abfrage mehrere Window-Funktionen nutzbar sind, die sich gegenseitig nicht beeinflussen.

Benötigt jemand zum Beispiel sowohl die Anzahl der Zeilen des Gesamtergebnisses als auch die Zeilen pro Kategorie, dann kann er die beiden Beispiele von oben auch in einer Abfrage gemeinsam verwenden.

Die Zeilen, die für eine Window-Funktion sichtbar sind, lassen sich weiter eingrenzen, wenn die Zeilen sortiert sind. Das macht »OVER« -Klauseln möglich, die zum Beispiel “alle Zeilen davor” oder “drei Zeilen davor bis drei Zeilen danach” bedeuten. Das “davor” und “danach” bezieht sich eben auf eine Sortierreihenfolge, die per »ORDER BY« in der »OVER« -Klausel frei festlegbar ist. Damit lässt sich zum Beispiel eine laufende Summe (“Summe über alle Zeilen davor”) oder auch ein gleitender Mittelwert (“Mittel über drei Zeilen davor bis drei Zeilen danach”) umsetzen (Listing 5), »wert« und »zeit« sind diesem Beispiel Spaltennamen.

Listing 5

ORDER BY mit OVER()

01 SELECT *
02  , SUM(wert) OVER (ORDER BY zeit
03  ROWS BETWEEN UNBOUNDED PRECEDING
04  AND CURRENT ROW)
05
06  , AVG(wert) OVER (ORDER BY zeit
07  ROWS BETWEEN 3 PRECEDING
08  AND 3 FOLLOWING)
09  FROM [...]

Die Formulierung des »BETWEEN« -Bereichs folgt ähnlichen Regeln wie »BETWEEN« in der »WHERE« -Klausel: Der Anfang muss vor dem Ende liegen und die benannten Werte selbst fallen noch in den Bereich. Die »AVG« -Funktion im Beispiel bildet also den Mittelwert über bis zu sieben Zeilen: die drei Zeilen davor, die aktuelle Zeile selbst und die nächsten drei Zeilen. Wenn es nicht genügend Zeilen zum Füllen des Fensters gibt, bleibt es eben kleiner – in der ersten Zeile zum Beispiel dann, wenn es keine drei Zeilen davor gibt.

Schließlich kann der Anwender die Fenstergröße nicht nur über Zeilen, sondern auch über Wertebereiche festlegen. So lassen sich Fenster definieren, die “drei Tage davor und drei Tage danach” abdecken – egal wie viele Zeilen diesen Tagen zugeordnet sind (Listing 6). Dazu wird der »BETWEEN« -Bereich mittels »RANGE« statt »ROWS« definiert.

Listing 6

Mit Wertebereichen

01 SELECT *
02  , AVG(wert) OVER (ORDER BY datum
03  RANGE BETWEEN INTERVAL '3' DAYS PRECEDING
04  AND INTERVAL '3' DAYS FOLLOWING)
05  FROM [...]

Neben den altbekannten Aggregat-Funktionen gibt es seit SQL:2003 auch so genannte Ranking-Funktionen, die zwingend eine »OVER« -Klausel mit »ORDER BY« benötigen.

Nicht nur Aggregat-Funktionen

Erwähnenswert sind »ROW_NUMBER« , »RANK« und »DENSE_RANK« . Mit »ROW _NUMBER« kann man, wie der Name vermuten lässt, Zeilen durchzählen. »RANK« und »DENSE_RANK« sind schon weniger intuitiv. Beide liefern einen Rang entsprechend der »ORDER BY« -Klausel. Der Unterschied zu »ROW_NUMBER« ist, dass ex aequo Platzierungen bei »RANK« und »DENSE_RANK« , wie im Sport üblich, denselben Rang erhalten. Es kann also zwei Erstplatzierte geben.

Dann stellt sich aber die Frage, welchen Platz der nächste belegt. Hier unterscheiden sich »RANK« und »DENSE_RANK« : Während »RANK« in diesem Fall einen Platz auslässt – also »3« liefert –, arbeitet »DENSE_RANK« mit lückenlosen Platzierungen – im Beispiel also »2« .

SQL:2011 führte weitere Window-Funktionen ein, um auf einzelne Zeilen eines sortierten Datenfensters zuzugreifen. Mit »LAG« und »LEAD« greift der Anwender auf die vorherige und nächste Zeile zu. »FIRST_VALUE« , »LAST_VALUE« und »NTH_VALUE« erlauben den Zugriff auf die erste, letzte oder n-te Zeile des Fensters. Das Beispiel in Listing 7 illustriert die Verwendung dieser Funktionen am Beispiel eines Wettbewerbes.

Listing 7

LAG und FIRST

01 SELECT RANK() OVER(ORDER BY punkte DESC) "Platz"
02  ,name
03  ,punkte
04  ,LAG(punkte) OVER(ORDER BY punkte DESC)-- Punkte "Rückstand auf Vorherigen"
05  ,FIRST_VALUE(punkte) OVER(ORDER BY punkte DESC)-- Punkte "Rückstand auf Ersten"
06  FROM [...]
07  ORDER BY punkte DESC

Die erste Spalte ermittelt die Platzierung mittels »RANK« . Danach folgen der Name und die erreiche Punktezahl. Die letzten beiden Spalten geben den Rückstand auf den Platz davor und auf den ersten Platz aus. Dazu greift die Routine mit »LAG« auf die Punkte des vorderen und mit »FIRST_VALUE« auf die Punkte des Erstplatzierten zu. Davon wird noch die Punktezahl des aktuellen Teilnehmers abgezogen (»-punkte« ), um die gewünschten Rückstände zu ermitteln.

Zu diesem Beispiel noch eine Warnung: Diese »OVER« -Klauseln verwendet »ORDER BY« , ohne das Fenster mittels »BETWEEN« einzuschränken. In diesem Fall schlägt eine Default-Klausel zu: »RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW« . Das ist gleich doppelt überraschend. Erstens: Nachfolgende Zeilen sind aus dem Fenster ausgeschlossen. Zweitens: Dieser Ausschluss findet auf »RANGE« -, nicht auf »ROWS« -Basis statt. Gleichplatzierte sind also sehr wohl noch im Fenster.

Und die Performance?

Der Hauptnutzen von Window-Funktionen sind die vielfältigen Möglichkeiten bei einfacher Syntax. Darüber hinaus sind Window-Funktionen in der Regel sogar deutlich schneller als das rein relationale Äquivalent. Trotz der umfangreichen Möglichkeiten, die Window-Funktionen bieten, lässt sich der Mehraufwand, den sie in der Datenbank verursachen, sehr einfach abschätzen. Dazu ist bloß zu bedenken, dass Window-Funktionen de facto immer in zwei einfachen Schritten ausgeführt werden: Vorbereitung und Anwendung.

In der Vorbereitungsphase ist sicherzustellen, dass die Daten entsprechend der »PARTITION BY« – und »ORDER BY« -Klauseln sortiert sind. Das heißt, die Spalten der »PARTITION BY« -Klausel werden – falls vorhanden – der »ORDER BY« -Klausel vorangestellt und die Daten dann dementsprechend sortiert. Diese Sortierung darf jedoch entfallen, wenn die Daten ohnehin in dieser Reihenfolge vorliegen. Das könnte sein, weil die Daten schon zuvor so sortiert wurden – zum Beispiel für eine andere Window-Funktion – oder, wie im letzten Beispiel, aufgrund einer globalen »ORDER BY« -Klausel. Die Sortierung kann aber auch entfallen, wenn die Daten aus einem Index mit entsprechender Sortierung stammen.

Der Mehraufwand der Vorbereitungsphase entspricht also im Wesentlichen dem Aufwand einer »ORDER BY« -Klausel für jede unterschiedliche »OVER« -Klausel. Gleichlautende »OVER« -Klauseln verursachen diesen Aufwand nicht mehrfach. Im letzten Beispiel fällt sogar überhaupt kein Vorbereitungsaufwand an, da die globale »ORDER BY« -Klausel die Daten ohnehin schon wie benötigt sortiert.

Nach der Vorbereitung kommen die einzelnen Window-Funktionen zur Ausführung. Dieser Aufwand lässt sich grob mit der normalen Verwendung von Aggregat-Funktionen mit »GROUP BY« ansetzen, nur dass die Funktion im schlimmsten Fall für jede Zeile ausgeführt wird. Natürlich setzen die Hersteller einiges daran, diesen schlimmsten Fall zu vermeiden.

Zeitreisen mit SQL:2011

Die letzte bahnbrechende SQL-Funktion, die dieser Artikel beschreiben möchte, sind die so genannten temporalen und bi-temporalen Tabellen. Obwohl der Begriff selbst nicht allzu viel hergibt, geht es dabei dennoch um Funktionen, die jeder schon mal gebraucht hätte. Ganz allgemein lassen sich damit Datenänderungen nachvollziehbar machen. Das erklärt das Beispiel einer Kundendatenbank: Wie geht der Anwender damit um, wenn jemand seinen Namen ändert? Genügt es, den Namen mit einem einfachen »UPDATE« zu überschreiben? Oder sollte nachvollziehbar sein, dass sich der Name geändert hat? Falls Nachvollziehbarkeit gefordert ist, musste der Anwender bisher selbst dafür sorgen: Also zum Beispiel die alte Zeile in der Kundendatenbank als alt kennzeichnen und eine neue mit dem neuen Namen anlegen.

Für diesen Fall bietet SQL:2011 nun Applikationszeit-versionierte Tabellen an. Ein »UPDATE« auf eine solche Tabelle kann dann so aussehen:

UPDATE kunden
 FOR PORTION OF gueltigkeit
 FROM DATE'2015-01-01'
 TO DATE'9999-12-31'
 SET name = 'Neuer Name'
 WHERE id = 123

Der Benutzer definiert also im »UPDATE« – unmittelbar nach dem Tabellennamen – für welchen Zeitraum der neue Name gültig ist. Dafür muss er die Tabelle entsprechend vorbereiten: zwei Spalten für Beginn- und Endzeitpunkt anlegen und diese zu einer Applikationszeit-Periode (im Beispiel »gueltigkeit« ) zusammenfassen. Den Rest erledigt SQL. Was auch immer nötig ist, um den gewünschten Zustand herzustellen, wird gemacht –zum Beispiel ein »UPDATE« auf die letztgültige Zeile und ein »INSERT« mit dem neuen Namen.

SQL:2011 bietet selbstverständlich auch alles andere an, was für den Umgang mit solchen Tabellen nötig ist: Das sind vor allem Constraints, die das Zeitkonzept verstehen (Stichwort »WITHOUT OVERLAPS« ), aber auch Abfragen zu einem bestimmten Zeitpunkt:

SELECT *
 FROM kunden
 WHERE id = 123
 AND gueltigkeit CONTAINS DATE '2015-12-01'

Diese Abfrage liefert den neuen Namen, da SQL-Perioden immer inklusive Beginn-, aber exklusive End-Zeitpunkt zu handhaben sind.

Die Applikationszeit-Versionierung ist dazu gedacht, die Realität widerzuspiegeln. Im Falle einer Namensänderung bedeutet dies, dass der Gültigkeitsbeginn des neuen Namens der Hochzeitstag ist – also nicht der Zeitpunkt, an dem das System den neuen Namen erfasst. Die Applikation kann die Gültigkeit frei wählen – daher auch der Name “Applikationszeit.”

Anders ist das bei den so genannten System-versionierten Tabellen. Sobald eine Tabelle durch entsprechende »ALTER TABLE« -Anweisungen System-versioniert ist, erledigt die Datenbank die Versionierung völlig transparent. Alle »SELECT« -, »UPDATE« – … Anweisungen funktionieren unverändert weiter – die Applikation muss also nicht geändert werden. Dafür kann die Applikation die Gültigkeitszeiträume auch nicht selbst wählen. Stattdessen verwendet die Datenbank immer die aktuelle Systemzeit.

Alle »INSERT« -, »UPDATE« – und »DELETE« -Anweisungen wirken immer ab sofort. System-Versionierung spiegelt also nicht die Realität wieder, sondern den Zeitpunkt, zu dem eine Änderung in der Datenbank vorgenommen wurde. Anders ausgedrückt: Wann das System davon Kenntnis erlangt hat.

Beide Formen der Versionierung – Applikationszeit- oder System-Versionierung – sind optional und lassen sich unabhängig voneinander verwenden. Es ist auch möglich, auf einer Tabelle beide Versionierungen gleichzeitig zu aktivieren, dann entstehen die eingangs erwähnten bi-temporalen Tabellen.

Das beantwortet dann auch die Frage, warum etwa ein Schreiben noch den alten Namen verwendet hat, obwohl sich der Name schon vor Wochen – zum Beispiel durch eine Hochzeit – geändert hat. Die Applikationszeit-Versionierung würde in diesem Fall den Hochzeitstag als Datum der Änderung angeben, die System-Versionierung jedoch den Zeitpunkt, an dem die Änderung im System eingetragen wurde. Das kann durchaus Wochen später sein – die wenigsten frisch Vermählten verwenden ihre Hochzeitsnacht darauf, die Namensänderung ihrer Bank bekannt zu geben.

Welche Datenbank kann’s?

Wer die eine oder andere hier beschriebene Funktion schon ausprobiert hat, musste vielleicht feststellen: Funktioniert nicht! Das liegt daran, dass nicht jede SQL-Datenbank eine moderne SQL-Datenbank ist. Genauer gesagt: Der SQL-Standard schreibt nur wenige Funktionen zwingend vor. Die Mehrzahl – auch alle hier beschriebenen Funktionen – sind optional. Außerdem gilt für viele Hersteller SQL:92 noch immer als Maß aller Dinge. Man stelle sich zum Vergleich die Schlagzeile vor: “Führende NoSQL-Datenbank unterstützt jetzt auch Windows 3.1.” Für die jüngeren Leser: Windows 3.1 erschien ebenfalls 1992.

All das ändert jedoch nichts daran, dass die modernen SQL-Funktionen nützlich sind, wenn sie auch erst einige Datenbanken anbieten. Darunter sind auch Open-Source-Datenbanken. Es kann sich auszahlen, modernes SQL zu lernen, um dann ganz bewusst eine Datenbank zu wählen, die die benötigten Funktionen unterstützt. Abbildung 3 gibt einen Überblick, welche Datenbanken die hier beschriebenen Funktionen bieten.

Abbildung 3: Verfügbarkeit ausgewählter SQL-Funktionen.

Abbildung 3: Verfügbarkeit ausgewählter SQL-Funktionen.

Der Autor

Markus Winand hat sich als selbstständiger Trainer darauf spezialisiert, Entwicklern den effizienten Umgang mit SQL näher zu bringen. Er hat dazu das Buch “SQL Performance Explained” herausgebracht und arbeitet derzeit an seinem nächsten Buch, das laufend auf http://modern-sql.com veröffentlicht wird.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben