SQL für lückenlose Historie

Wenn man vor der Aufgabe steht, eine lückenlose Historie zu verwenden, greift man schnell zu Datumsfeldern „gültig von“ und „gültig bis“. Das Problem dabei ist, dass man sicherstellen muss, dass weder Lücken noch Überschneidungen entstehen. Beispiele für so eine Historie können Gehälter oder Preise sein, deren Betrag man zu einem bestimmten Datum abfragen möchte.

Eine einfache und elegante Methode diese Bedingungen sicherzustellen ist die Verwendung nur eines „gültig ab“-Datums. Dann ist dieser Wert gültig bis zum nächsten „gültig ab“-Wert. Diesen Wert kann man mit einer Subquery abfragen.

Hier ein Beispiel. Es geht um die Bestimmung eines Preises zu einem Artikel an einem Datum (z.B. Verkaufsdatum). Wir brauchen eine Tabelle mit den Artikeln und eine für die Preise:

Preisliste Tabellendefinitionen

PreislisteDaten

Basierend auf der Tabelle mit den Preisen erstellt man eine Abfrage, die den Gültigkeitszeitraum zurückgibt. Für das „gültig bis“-Datum verwendet man eine Subquery, die das Minimum des „gültig ab“-Datums zurückgibt, das größer sein muss als das aktuelle „gültig ab“-Datum.

gültig bis: (SELECT min([gültig ab]) FROM tblArtikelPreise as subQueryBis WHERE subQueryBis.id_artikel = tblArtikelPreise.id_artikel and subQueryBis.[gültig ab]>tblArtikelPreise.[gültig ab])

PreislisteQuery1Def

PreislisteQuery1DefRes

Wie man sieht, hat der letzte gültige Preis kein „gültig bis“. Um die Abfrage aber später in Abfragen nutzen zu können, sollte man dort ein Datum zurückgeben. Ich verwende dafür immer den 31.12.3000. Für Access sieht die Felddefinition dann so aus:

gültig bis: Nz((SELECT min([gültig ab]) FROM tblArtikelPreise as subQueryBis WHERE subQueryBis.id_artikel = tblArtikelPreise.id_artikel and subQueryBis.[gültig ab]>tblArtikelPreise.[gültig ab]);#31.12.3000#)

Für MySQL sieht das Feld z.B. so aus:

cast(
   ifnull(
   (
     SELECT
        min(`preise`.`gültig ab`)
     FROM
        `tblArtikelPreise` `preise`
     WHERE
        (
           (
              `preise`.`id_artikel` = `tblArtikelPreise`.`id_artikel`
           )
           AND (
              `preise`.`gültig ab` > `tblArtikelPreise`.`gültig ab`
           )
       )
   ),
   '3000-12-31'
   ) AS date
) AS `bis`

Das Resultat der Abfrage sieht dann so aus:

PreislisteQuery2DefResDiese Abfrage kann man dann in der eigentlichen Preisabfrage verwenden. Access kann leider keine anderen JOINs als „=“ darstellen. Will man die Abfrage weiterhin mit dem Editor bearbeiten können muss man den Filter auf das Datum als Bedingung einbauen, was die Abfrage langsamer macht. Die sieht dann z.B. so aus:

qryDef1

qryDef1Res

Schneller geht es, wenn man die Datums-Bedingung im JOIN verarbeitet. MySQL und der SQL-Server können diese JOINs im Editor darstellen (Oracle bestimmt auch). Access kann diese JOINs immerhin verarbeiten. Man muss diese dann direkt im SQL angeben:

qryDef2Das Ergebnis sieht dann genauso aus:

qryDef2Res

Viel Spaß.

 

2 Kommentare zu „SQL für lückenlose Historie

  1. Hallo Tobi,
    danke für die Threema-Info! Dieses Prinzip hatte Fernando schon bei der ZEK/PEP für die Teamzugehörigkeits-Historie verwendet. Allerdings sind in dieser auch mehrere Einträge mit gleichem „gültig ab“-Datum möglich (Mitarbeiter/in kann mehrmals am gleichen Tag das Team wechseln), sodass zusätzlich der Eintragszeitpunkt (AddDateTime) ausgewertet werden muss.
    Viele Grüße
    Christof
    PS. was macht Ida?

    • Wenn nur ein Wert pro Tag gültig sein darf, würde ich die alten am gleichen Tag zwecks Dokumentation in eine Log-Tabelle abspeichern und ersetzen. Wenn es mehrere gültige Einträge an einem Tag geben soll (vormittags einen anderen Preis als nachmittags) muss man in dem Datumsfeld nur die Uhrzeit mit speichern. Halt datetime-Felder statt date-Felder nehmen. Die Lösung von Fernando damals war eher suboptimal und führte doch zu komplizierten SQLs.