Bi-temporale Datenmodellierung oder Filtern bzw. Vor- und Zurückspulen in 2 Zeitdimensionen: Gültigkeit und Erfassungszeitpunkt
Nicht selten will man die Daten in einer Anwendung historisieren, indem man die entsprechende Zeile bei einer Änderung nicht einfach überschreibt, sondern eine neue Zeile anlegt. In der Regel soll dann die Anwendung einfach den aktuellen Stand anzeigen. Manchmal hat der Anwender aber den Wunsch, die Daten so zu sehen, wie sie für einen Zeitpunkt in der Vergangenheit gegolten haben oder in der Zukunft gelten werden. Dies stellt spezielle Ansprüche an die Datenmodellierung in der Datenbank und auch an die Programmierung der Anwendung.
Wenn Daten historisiert werden sollen, so kommen 2 zeitliche Aspekte ins Spiel: Erstens die Gültigkeit der Daten. Beispiel: Adressänderungen. Eine Person zieht um und meldet ihrer Bank im Voraus die neue Adresse, gültig ab dem ersten Tag des Monats in 2 Monaten. Im Idealfall erfasst die Bank die neue Adresse mit dem Gültigab-Datum, behält aber gleichzeitig die bisherige Adresse. Korrespondenz wird so lange an die alte Adresse geschickt, bis das neue Gültigab-Datum erreicht wird. Ab dann geht ein Postversand an die neue Adresse. Die Anwendung soll dann entweder die Daten zeigen, die zu einem eingestellten Datum – i.d.R. heute – gültig sind, oder aber eine Übersicht zeigen mit der Historie der Daten bezüglich ihrer Gültigkeit. Im ersten Fall kann der Benutzer das gewünschte Gültig-per-Datum in der Anwendung von der Gegenwart in die Vergangenheit oder in die Zukunft stellen und sieht dann die Daten entsprechend gefiltert.
Der zweite zeitliche Aspekt ist der des Erfassungszeitpunktes. Man will festhalten, wer wann welche Änderung erfasst hat – notabene für den gleichen Gültigkeitszeitpunkt. Man spricht hier deshalb auch von der Audit-Zeitdimension. Die Anwendung zeigt auch hier in der Regel die aktuellen Daten, erlaubt es aber bei Bedarf, die für einen bestimmten gleichen Gültigkeitszeitpunkt gemachten Änderungen aufzulisten. Beispielsweise wurde die Hausnummer in einer Adresse gültig per 1. November nachträglich korrigiert. Dann soll ersichtlich sein, wie die Adresse vorher und nachher (immer gültig per 1. November) ausgesehen hat und wer wann die Datenerfassungen vorgenommen hat.
Wie setzt man dies nun um? Wenn man nur eine der beiden zeitlichen Aspekte historisieren will, so ist dies weniger schwierig. Wenn man beispielsweise lediglich Datenkorrekturen mit ihrem Erfassungszeitpunkt wegschreiben will, so setzt man dies Datenbank-technisch mit einem sogenannten Trigger für Änderungen und Löschungen auf den entsprechenden Tabellen um. Der Trigger fängt jegliche derartigen Änderungen ab und schreibt vorher zusätzlich zur gewünschten Aktion die Zeile mit den jeweils zuvor bestehenden Datenwerten in eine Schatten- oder Journal-Tabelle. Neben der Tabelle mit den aktuellen Werten hat man dann also auch eine Journal-Tabelle mit historischen Werten in der Datenbank. Im folgenden Beispiel umgesetzt anhand einer Personen-Tabelle mit Geburtsdatum und Kundenkategorie:
Dieser Ansatz hat den Vorteil, dass die eigentliche Tabelle nicht durch historische Einträge vergrössert wird und die Anwendung diese nicht immer wegfiltern muss – solange man nur die aktuellen Werte sehen will, was wohl meistens der Fall ist.
Will man andererseits lediglich die andere Zeit-Dimension – die der Gültigkeit – festhalten, so kann man zunächst einfach eine weitere Tabellenspalte mit dem Gültigab- und eventuell einem Gültigbis-Datum einführen. Eine Personen-Tabelle enthält dann für verschiedene Personen ein bis mehrere Einträge mit unterschiedlichen Gültigkeitszeitperioden für sich ändernde Kundenkategorien (Bronze, Silber, Gold), die je nach Umsatz angepasst wird.
Die Anwendung zeigt dann entweder alle Personen-Einträge an oder nur die per heute gültigen. Zusätzlich dazu ist sie in der Lage, die zu einem anderen – wählbaren – Zeitpunkt gültigen Einträge anzuzeigen. Die Filterung auf einen bestimmten Zeitpunkt erfolgt dabei entweder durch die Anwendung oder durch die Datenbank, indem die Anwendung immer über eine View auf die entsprechende Tabelle zugreift. Die View filtert die Tabelleneinträge und zeigt nur die für den gewählten Zeitpunkt gültigen Einträge. Die Anwendung muss der Datenbank mitteilen können, mit welchem Gültigkeitsdatum sie arbeiten soll. Dies wird in der Regel in einer sogenannten Session-Variablen in der Datenbank festgehalten und bei jedem Datenzugriff der Anwendung in den Views angewendet.
Eine Schwierigkeit haben wir dabei aber noch ausgeblendet, nämlich dass bei einer relationalen Datenmodellierung Einträge in einer Tabelle über sogenannte Foreign Keys auf Einträge in einer anderen Tabelle verweisen: Die Person 1 hat die Adresse 28.
Wie sollen nun solche ForeignKey-Verweise funktionieren, wenn sowohl die Einträge in der Personen- als auch in der Adress-Tabelle ändern können und dabei die Nummern in der Id-Spalte hochgezählt werden? Wenn also die Person 37 wegen einer Adressänderung nicht mehr die Adresse 523, sondern 524 hat? – Dies löst man so, indem man die Tabellenspalten auf 2 getrennte Tabellen verteilt, eine Key-Tabelle mit den unveränderlichen Attributen mit insbesondere dem fachlichen Schlüssel – im Falle einer Person als Kunde wird dies die Kundennummer sein – und eine Zustandstabelle mit den veränderlichen Attributen. Zu einem Key-Eintrag gibt es 1 bis mehrere Zustandseinträge. So kann dann der Key-Eintrag für eine Person auf den immer gleichbleibenden Adress-Key-Eintrag verweisen.
Über eine View werden dann jeweils die Daten aus der Key- mit den Daten aus der Zustandstabelle wieder zusammengeführt und via gesetztem Gültigkeitszeitpunkt in der Sessionvariablen gefiltert. Die Anwendung arbeitet nicht gegen die Tabellen, sondern gegen die Views, und macht auch Inserts, Updates und Deletes gegen die Views. Der oben genannte Trigger wird nicht auf der Tabelle angelegt, sondern auf der View. Der Trigger zerlegt Änderungen in solche auf der Key-Tabelle (nur Inserts) und auf der Zustandstabelle. Auch auf der Zustandstabelle werden nie Einträge mutiert oder gelöscht, sondern es wird zwecks Historisierung immer ein neuer Eintrag mit den neuen Zustandsdaten angelegt (bei einer Löschung mit einem Lösch-Flag).
Will man nun beide Zeitdimensionen – Gültigkeit und Erfassungszeitpunkt – gleichzeitig historisieren, so geht man gleich vor, indem man die Tabellen aufteilt in Key- und Zustandstabellen, die über eine View für die Anwendung wieder zusammengeführt werden. Die View muss aber in diesem Fall nicht nur auf den gewünschten Gültigkeitszeitpunkt filtern, sondern über eine zweite Sessionvariable auch für den Erfassungszeitpunkt!
Die iRIX hat die bi-temporale Datenmodellierung in verschiedenen Kundenprojekten (Pensionskassen, Gesundheitsbehörden) erfolgreich und mit verschiedenen Datenbank-Technologien (Oracle, PostgreSQL, SQL-Server) umgesetzt. Wichtig war dabei auch, dass für den Anwender trotz der Views mit den zeitlichen Filtern die Antwortzeiten der Anwendung immer kurz geblieben sind, auch für grosse Tabellen.