SQL: mit Select auf „Nachbar-Zeilen“ zugreifen

Möchte man mit SQL auf Nachbar-Zeilen in der gleichen Tabelle zugreifen, so ging das früher nur mit komplizierten Self-JOINs – SQL:2011 macht das einfacher und geht auch auf der IBM i.

Angenommen, man hat eine Tabelle die tägliche Eröffnungs- und Endbestände ausweist:

RowDatumStartEnde
12019-12-011.280,501.450,78
22019-12-021.450,78760,62
32019-12-03750,62983,12
42019-12-04983,121.133,47
52019-12-051.133,47546,33

Wie man sieht, stimmt der Start-Wert am 3.12. nicht mit dem End-Wert am 2.12. überein.

Um solche Fehler zu finden wurden früher klassisch Programme oder SQL-Anweisungen mit Self-JOIN (also Verknüpfung der Tabelle mit sich selbst) geschrieben.

Heute geht so etwas einfacher, denn SQL:2011 hat da eine Menge hinzugelernt. Mit den Funktion LAG() und LEAD() kann relativ bequem auf die Zeile vor oder nach der aktuellen Zeile zugreifen.

Hier das passende Beispiel zu obiger Tabelle:

WITH Ende_Start_Differenz AS
(
  SELECT
    Datum,
    StrBetr,
    EndBetr,
    (StrBetr - LAG(EndBetr) OVER(ORDER BY Datum)) AS EndStrDiff
  FROM Tabelle
)
SELECT * FROM Ende_Start_Differenz
WHERE EndStrDiff <> 0

Die „Magie“ liegt in der Funktion LAG() mit der Erweiterung OVER(). LAG() greift aus die vorherige Zeile zu – holt sich also den Wert Ende aus der Zeile vorher. Damit LAG() weiss, welche Zeile die vorherige ist, muss mit OVER(ORDER BY …) geklärt werden, in welcher Reihenfolge die Zeilen denn verarbeitet werden.

Analog zu LAG() gibt es noch LEAD() – dieses greift dann auf die direkt nachfolgende Zeile zu.

Da LAG() und LEAD() nur im SELECT-Bereich funktionieren, und nicht in der WHERE-Klausel, muss man die Tabelle über eine WITH Klausel um das berechnete Feld ergänzen und dann über einen weiteren SELECT auswerfen.

Übrigens eine Technik, die man auch öfter gebrauchen kann, wenn es darum geht einen Wert zu berechnen und dann daraus zu selektieren – dann muss man die Berechnung nur 1x schreiben und bei WHERE nicht wiederholen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.