SQL

Aufbau eines Verlaufsdatenmodells unter PL/SQL Dipl.-Math. Hans-Friedrich Pfeiffer debitel AG Niederlassung Elmshorn Schlüsselworte: "Assoziative Arra...
Author: Gretel Beutel
12 downloads 1 Views 159KB Size
Aufbau eines Verlaufsdatenmodells unter PL/SQL Dipl.-Math. Hans-Friedrich Pfeiffer debitel AG Niederlassung Elmshorn Schlüsselworte: "Assoziative Array" Verlaufsdatenmodell PL/SQL Einleitung: Dieser Artikel befasst sich mit dem Aufbau eines Verlaufdatenmodells unter PL/SQL. Hierzu werden zunächst das Datenmodell für Verlaufstabellen prinzipiell beschrieben und deren Vorteile für die Endanwender dargestellt. Es werden dann Kriterien an Datenstrukturen und Programmabläufen für die Erstellung einer Laufzeit-Tabelle formuliert. Anhand von BeispielCode in PL/SQL wird gezeigt, wie sich bestimmte Anforderungen in PL/SQL mit Assoziativen Arrays realisieren lassen. Verlaufsdatenmodell Unabhängig vom gewählten Datenmodell einer Data Warehouse-Umgebung wird es stets eine Reihe von Tabellen geben, die Bewegungsdaten enthalten – sogenannte Faktentabellen. In der Regel sind Faktentabellen sehr umfangreich und können mehrere hundert Millionen Datensätze umfassen. Faktentabellen stellen hierbei quantitative Zustände eines Ereignisses zu einem definierten Zeitpunkt dar: etwa die bestellte und bislang ausgelieferte Menge eines Produktes für einen Kunden zum einem bestimmten Zeitpunkt. Abfragen auf Faktentabellen werden eher das Ziel haben, sich einen genauen Überblick über quantitative Zustände zu verschaffen, etwa den Zusammenhang zwischen Urlaubszeiten und Anzahl von Bestellungen. Sollen unterschiedliche Ereignisse - wie etwa Vertragsabschluß, Vertragskündigung, Kündigungsrücknahme, etc. – über Zeiträume hinweg betrachtet werden, so reicht die Struktur einer reinen Faktentabelle hierfür nicht aus. Vielmehr muss ein Schwerpunkt dann auf die Zeiträume gelegt werden und es muss eine Struktur geschaffen werden, die es erlaubt, unterschiedliche Ereignisse horizontal anzuordnen. Damit lassen sich Fragen etwa nach Bestand der aktiven und deaktiven Kunden zu einem Zeitpunkt / Zeitraum beantworten. Eine solche Tabelle zeichnet sich dadurch aus, dass es für jeden Datensatz jeweils ein Datumsfeld für den Beginn des Ereignisses und ggf. dessen Ende aufweist. Auf diese Weise können unterschiedliche Ereignisse mit Zeitreihen in einer einheitlichen Tabelle verwaltet werden. Eine solche Tabellenstruktur wird im Folgenden als Verlaufsdatenmodell oder Laufzeitmodell bezeichnet.

Nachfolgend ist ein Beispiel für die Struktur einer Verlaufsdatentabelle, die für einen Kunden anzeigt, wann er aktiviert, sein Produkt gewechselt und schließlich das Netz geändert hat. Kunden-Nr. Ereignis Ereignis-Start-Datum Ereignis-Ende-Datum Produkt Produktwechsel Altes Produkt Netz Netz-Wechsel Altes Netz Kündigung … Aktuell

12345 Aktivierung 01.01.2008 20.01.2008 P001 nein N001 nein nein

12345 Produktwechsel 21.01.2008 14.05.2009 P002 ja P001 N001 nein nein

12345 Netzwechsel 15.05.2009 P002 nein P001 N002 ja N001 nein

nein

nein

ja

Wird für einzelne Ereignisse ein gesondertes Feld hinzugefügt, welches angibt, ob ein Wechsel stattgefunden hat, so können Ereigniswechsel für ausgewählte Zeiträume bequem abgefragt werden. Eine hilfreiche Idee ist es, dem Datenmodell eine gesonderte Spalte (etwa "aktuell") hinzuzufügen, bei welcher vermerkt wird, ob der angesprochene Datensatz in der Historie der zuletzt eingetragene (und damit gültig) ist. Mit diesem Konstrukt lassen sich Abfragen für aktuelle Daten dann einfacher realisieren. Besondere Aufmerksamkeit haben diejenigen Datumsfelder, die den Beginn und das Ende eines Ereignisses beschreiben. Die Tabelleninhalte sollten so aufbereitet werden, dass es für eine Entität zwischen dem ersten Ereignis und dem letzten Ereignis zu jedem Zeitpunkt genau ein Ereignis eingetragen wird. Dies impliziert, dass es in der Reihe der Ereignisse keine zeitlichen Lücken gibt und SQL-Statements immer bei einer Stichtag-Abfrage genau ein Ergebnis zurückliefern. Für das Ende-Datum bedeutet dies, dass es um eine Einheit (Minute, Stunden, Tag) weniger als das nachfolgende Start-Datum gesetzt werden sollte. Bei Aufbereitung der Daten für tagesgenaue Auswertungen wäre das Ende-Datum der Vortag des nachfolgenden Ereignisses. In der Regel wird man mit tagesgenauen Auswertungen auskommen können. Unter den oben genannten Bedingungen bedeutet dies jedoch, dass es an einem Tag nur ein Ereignis geben darf. Anders formuliert: gab es zu einem Tag mehrere Ereignisse, so muss sorgsam ausgewählt werden, welches Ereignis als das zu dem Tag gültige genommen werden soll. Bei mehreren Produktwechseln am gleichen Tage wird sicher das zuletzt gewählte in das Modell übernommen. Andere Ereignisse am gleichen Tage könnten sich sogar vollständig aufheben: etwa eine Kündigung mit anschließender Kündigungsrücknahme.

Anforderungen für den Aufbau eines Verlaufdatenmodells Es ist damit zu rechnen, dass die Quelldaten für das Verlaufsdatenmodell nicht verarbeitungsgerecht im Quellsystem vorliegen. Überdies werden benötigte Daten auf mehreren Tabellen verteilt liegen, wobei hier meist eine 1:n, in manchen Fällen aber auch eine m:n-Beziehung vorliegen dürfte. Für die Programmierung wird daher eine Datenstruktur ermöglicht, die die genannten Beziehungen platzsparend und performant zur Verfügung stellen kann. Für das Verlaufsdatenmodell müssen vorab eine Reihe von Plausibilitäten und Bedingungen definiert werden, die unbedingt berücksichtigt werden müssen. Beispiele hierfür wären etwa, dass eine Rücknahme einer Kündigung nur nach vorhergehender Kündigung erfolgen darf oder ein beendetes Kundenverhältnis keinesfalls als "aktiv" gekennzeichnet sein darf. In Abhängigkeit von der Datenqualität im Quellsystem wird es möglicherweise notwendig sein, bestimmte Ereignisse künstlich zu generieren (etwa eine im Quellsystem fehlende Kündigung vor einer Kündigungsrücknahme) oder aber auch zu ignorieren, wie etwa in dem Fall, dass am gleichen Tag eine Kündigung und eine Kündigungsrücknahme erfolgt. Die Plausibilitäten münden dann in ein meist sehr komplexes Regelwerk, mit dessen Hilfe Grunddaten aus einer Reihe von Quelltabellen in das Format der Laufzeittabelle gebracht werden. Das Regelwerk selbst wird zum größten Teil aus einer Vielzahl von Bedingungen bestehen, so dass es sich aus Gründen der Wiederverwendbarkeit und der Dokumentation empfiehlt, diese als Funktionen zu definieren. Die oben genannten Beispiele zeigen bereits, dass der Aufbau eines Eintrages im Verlaufsdatenmodell unter Umständen abhängig ist vom Vorgängerdatensatz oder auch des nachfolgenden Datensatzes. Ein Verlaufsdatenmodell macht es in jedem Falle erforderlich, auf möglichst einfache und sparsame Art die Daten des Vorgängers und des Nachfolgers einzusehen. Von der Möglichkeit, diese im Bedarfsfalle explizit aus der Datenbank zu selektieren, muss abgeraten werden: hier würde unter Umständen eine erhebliche Last durch ständige Mini-Selektionen auf die Datenbank gebracht. Vielmehr sollte das Ziel sein, alle für das Verlaufsmodell benötigten Daten in einer gemeinsamen Struktur zu halten, um so auf einzelne Elemente direkt zugreifen zu können. Da über die Anzahl der zu verarbeitenden Datensätze zu einer Entität (etwa Vertrag oder Kunde) keine allgemeinen Aussagen vorliegen, brauchen wir eine dynamisch wachsende Datenstruktur, die beliebig viele Elemente aufnehmen kann. Bezogen auf die Implementierung ( nicht nur unter PL/SQL ) wird an dieser Stelle schon deutlich, dass am sinnvollsten zusammengesetzte Datentypen verwendet werden, die zudem in dynamisch wachsenden Arrays abgespeichert werden können. Dynamische Datenstrukturen in PL/SQL : Assoziative Arrays (PL/SQL-Tabellen) Assoziative Arrays sind eindimensionale Listen eines beliebigen, dann aber festgelegten Datentyps. Als Datentypen kommen auch types und records in Frage, so dass komplexe und

verschachtelte Strukturen aufgebaut werden können. Das Besondere bei Assoziativen Arrays ist die Art der Indizierung: diese lässt sowohl INTEGER als auch VARCHAR2 zu und ermöglicht den Aufbau eines Arrays "mit Lücken". Nur tatsächlich verwendete Indices belegen Speicherplatz, die dazwischen liegenden Indices existieren nicht. Die Art der Indizierung lässt ein Durchlaufen der Elemente über eine FOR-Schleife nicht zu. Stattdessen gibt es in PL/SQL spezielle Funktionen, mit denen das erste, das letzte, der Nachfolger und der Vorgänger eines Elementes bestimmt werden kann. Beispiele hierzu erfolgen weiter unten. Für die Entwicklung eines Laufzeitdatenmodells kann es ausschlaggebend sein, was für eine Indizierung verwendet werden soll. In der Regel basiert ein Laufzeitmodell auf Ereignissen, die zu einem bestimmten Zeitpunkt stattfanden. Durch die Konvertierung eines DATE-Feldes in ein VARCHAR2 lässt sich auch ein Sekunden-genauer Zeitpunkt als Index verwenden. Da die Schlüssel alpha-numerisch sortiert werden, sollte die Konvertierung stets vom "Jahr" an abwärts eingestellt werden (z.B. to_char( StartDatum, 'yyyymmdd hh24:mi:ss'). Eine andere Reihenfolge des Konvertierungsstrings mag sonst eine falsche alpha-numerische Reihenfolge beim Durchlaufen eines Assoziativen Arrays erzeugen. In den meisten Fällen wird es ausreichend sein, Ereignisse auf Tagesbasis zu betrachten. Dieser Ansatz birgt jedoch auch einige Probleme in sich, auf die wir weiter unten zurückkommen werden. Komplexe Datenstrukturen lassen sich in PL/SQL auch mit VARRAYs und NESTED TABLEs erstellen – manche der oben aufgeführten Kriterien werden aber von diesen Strukturen nicht erfüllt. Assoziative Arrays entsprechen Hashes in anderen Programmiersprachen, so dass wir auch diesen Ausdruck im Weiteren als Synonym für Assoziative Arrays verwenden werden. Schritte zur Implementierung eines Verlaufdatenmodells Aus Gründen der Performance und der Übersichtlichkeit sollten alle zum Aufbau eines Verlaufdatenmodells benötigten Daten – außer Lookup-Tabellen - in einer gemeinsamen Struktur vorgehalten werden. Zum einen soll erreicht werden, dass alle Daten nur einmal aus der Datenbank selektiert werden und dann beliebig zur Verfügung stehen, zum anderen erhöht sich die Übersichtlichkeit des Programmcodes, wenn an Funktionen und Prozeduren nur wenige Übergabeparameter mitgegeben werden müssen. In diesem Sinne ist die Art der Implementierung von der gewählten Datenstruktur stark abhängig. Ist eine geeignete Struktur gefunden, so ist die nächste Aufgabe die Befüllung der Struktur mit Quelldaten. Hierbei können durch bestimmte Datenkonstellationen gewisse Problematiken auftreten – daher werden drei gesonderte Fälle von Datenkonstellationen beispielhaft herausgestellt und gezeigt, wie man programmtechnisch damit umgehen kann. Schließlich muss ein Regelwerk definiert werden, dass die Quelldaten in das Verlaufsdatenmodell verwandelt. Hier kann nur eine grobe Strukturierung gezeigt und auf wichtige Fragestellungen hingewiesen werden.

Aufbau der benötigten Datenstrukturen Wir gehen im Folgenden stillschweigend davon aus, dass wir für den Aufbau eines Laufzeitmodells den Lebenszyklus einer konkreten Entität (etwa ein bestimmter Vertrag oder Kunde) betrachten. Alle für das Laufzeitmodell benötigten Quelldaten müssen mit einem Zeitpunkt versehen sein – i.d.R. ein DATE-Feld -, den wir als das Startdatum eines Ereignisses betrachten. Unter diesem Startdatum werden alle Daten in einer gemeinsamen Struktur verwaltet. Aus der Struktur heraus soll zudem ersichtlich sein, auf welcher QuellTabelle die Daten beruhen – damit wird es möglich, mehrere unterschiedliche Ereignisse zum gleichen Startdatum betrachten zu können. Eine solche Struktur der Zusammenfassung insbesondere gleichzeitig stattfindenden Ereignisse hat den Vorteil, dass sich Plausibilitätsprüfungen nur auf die eigentliche Datenstruktur formulieren lassen und damit der zu entwickelnde Quellcode an Übersichtlichkeit gewinnen. Nachfolgend wird an Hand eines Beispiels der PL/SQL-Code für den Aufbau der Datenstruktur gezeigt. Wir gehen davon aus, dass wir 3 Quelltabellen haben, Tabelle_1 bis Tabelle_3, die – der Einfachheit halber – alle den folgenden Aufbau aufweisen: CREATE TABLE TABELLE_x ( COL1 VARCHAR2(10), StartDatum DATE, Seq NUMBER(10) )

Im Allgemeinen werden für die einzelnen Quelltabellen unterschiedliche Bedingungen vorzufinden sein: solche, die für unsere Entität und Startdatum eindeutig sind, solche, die mehrere Datensätze zurückliefern, schließlich solche Tabellen, aus denen nur wenige Spalten überhaupt betrachtet werden sollen. Derartige Randbedingungen haben entscheidenden Einfluss für die Programmierung der Datenbereitstellung. Zur Illustration werden wir daher die folgenden Bedingungen an unsere Quelltabellen festlegen: Für alle Quelltabellen soll die Date-Spalte STARTDATUM jeweils das Startdatum eines Ereignisses (auf Tagesbasis) sein. Tabelle_1 soll für eine Entität und Startdatum mehrere Datensätze bereitstellen, wobei aber der zeitlich letzte Datensatz pro Tag maßgeblich sein soll. Für Tabelle_2 wollen wir davon ausgehen, dass nur die Spalten COL1 und STARTDATUM betrachtet werden sollen, für Tabelle_3 gehen wir davon aus, dass es zum Startdatum einer Entität beliebig viele Datensätze geben kann. Die Spalte SEQ ist ein eindeutiger Schlüssel der Tabelle ( z.B. eine Sequence ). Für die Randbedingung bei Tabelle_2 werden wir eine View erzeugen, die uns nur die gewünschten Spalten zurückliefert: create view v_tabelle_2 as select col1, StartDatum from tabelle_2;

Nunmehr lässt sich der Aufbau der Datenstruktur wie folgt schreiben:

create or replace package verlauftest as type tabelle_3_hash is table of tabelle_3%rowtype index by pls_integer; type zieltab_hash is table of zieltabelle%rowtype index by pls_integer; type quelldaten_type is record ( tabelle_1 flag_tabelle_1

tabelle_1%rowtype, boolean,

-- man beachte die Verwendung der zuvor erstellten View tabelle_2 v_tabelle_2%rowtype, flag_tabelle_2 boolean, -- man beachte die Verwendung des zuvor erstellten Hashes für Tabelle_3 tabelle_3 tabelle_3_hash, flag_tabelle_3 boolean, zieltabelle zieltab_hash, zieltabelle_flag boolean ); type quelldaten_hash is table of quelldaten_type index by pls_integer; procedure FuelleDatenStruktur ( verlauf IN OUT NOCOPY quelldaten_hash ); procedure AufbauVerlaufsdaten ( verlauf IN OUT NOCOPY quelldaten_hash ); end verlauftest;

Ein wesentlicher Punkt ist die Verwendung von %rowtype. In dem Falle, dass nicht alle Spalten einer Tabelle abgespeichert werden sollen, lässt sich die Auswahl der benötigten Spalten durch eine View realisieren, die anstelle der Tabelle dann verwendet werden kann. Im obigen Beispiel haben wir dies zur Illustration bei Tabelle_2 so erzeugt. Für Tabelle_1 kann zu einem Startdatum nur ein einziger Datensatz gespeichert werden – dies entspricht der Forderung, dass für Tabelle_1 nur der letzte Datensatz für das Startdatum zählt. Anders ist dies für Tabelle_3: dort gibt es zu einem Startdatum ebenfalls mehrere Ereignisse, nur kann zum Zeitpunkt der Selektion noch nicht entschieden werden, welcher dieser Datensätze für den weiteren Verlauf benötigt werden. Hier müssen also für das gleiche Startdatum mehrere Datensätze abgespeichert werden. Aus diesem Grund wird in unserer Datenstruktur für Tabelle_3 ein Hash verwendet, dessen Schlüssel anderweitig als das Startdatum sein muss. Ein weiterer wichtiger Punkt ist, dass es zu jeder Tabelle ein gesondertes Bool'sches Feld gibt (flag_tabelle_x), welches später dazu verwendet wird um festzustellen, ob Daten aus einer bestimmten Quelltabelle vorliegen oder nicht. Schließlich enthält unsere Datenstruktur auch eine Struktur unserer Ziel-Tabelle als Assoziatives Array mit Startdatum der einzelnen Ereignisse als Schlüssel.

Von unserer Datenstruktur quelldaten_type wird schließlich ein Hash erzeugt, der über einen rein numerischen Wert aufgebaut wird. Hierzu werden wir dann später das Startdatum der Ereignisse als Zahlwert umrechnen. Unsere Datenstruktur lässt sich nun wie folgt interpretieren: Zu jedem Ereignis, dass in einer der Quelltabellen vorliegt, gibt es einen Zeitpunkt, an welchem das Ereignis stattgefunden hat. Dieses Datum wird als Index für den Hash verwendet, der alle zu dem Startdatum gehörenden Quelldaten speichert. Der gesamte Verlauf ergibt sich daher aus allen Listenelementen des Hashes und kann dann in die Form der Zieltabelle überführt werden. Nachfolgend ein Beispiel, wie die mit Daten gefüllte Struktur aussehen könnte: quelldaten_hash: col1 StartDatum --TRUE

tabelle_1 tabelle_1_flag [01.01.2009] v_tabelle_2 v_tabelle_2_flag [1]

= 'Aktivierung' = 01.01.2009

NULL FALSE tabelle_3

col1 StartDatum Seq

= 'ChngProd' = 01.01.2009 =1

tabelle_3

col1 StartDatum Seq

= 'ChngOpt' = 01.01.2009 =2

tabelle_3_hash [2]

Die Indices sind fett und in eckigen Klammern gesetzt. Befüllen des Hashes Als zweiter Schritt folgt das Befüllen des Hashes. Hierzu werden für eine Entität (Kunde, Vertrag, Auftrag) aus allen Grundtabellen die entsprechenden Daten gezogen und dann, unter Beachtung der Besonderheiten, wie sie oben als Beispiel definiert worden, in unsere Datenstruktur eingefügt:

create or replace package body verlauftest as procedure FuelleDatenStruktur ( verlauf IN OUT NOCOPY quelldaten_hash ) is

cursor csr_1 is select * from tabelle_1 order by StartDatum; cursor csr_2 is select … from v_tabelle_2; cursor csr_3 is select * from tabelle_3; daten_tabelle_1 tabelle_1%rowtype; daten_tabelle_2 v_tabelle_2%rowtype; daten_tabelle_3 tabelle_3%rowtype; Index varchar2(8); begin open csr_1; loop fetch csr_1 into daten_tabelle_1; exit when csr_1%NOTFOUND; Index := to_char(daten_tabelle_1.Startdatum, 'yyyymmdd' ); verlauf( Index).tabelle_1 := daten_tabelle_1; end loop; close csr_1; open csr_2; loop fetch csr_2 into daten_tabelle_2; exit when csr_2%NOTFOUND; Index := to_char(daten_tabelle_2.Startdatum, 'yyyymmdd' ); verlauf( Index ).tabelle_2 := daten_tabelle_2; end loop; close csr_2; open csr_3; loop fetch csr_3 into daten_tabelle_3; exit when csr_3%NOTFOUND; Index := to_char(daten_tabelle_1.Startdatum, 'yyyymmdd' ); verlauf( Index ).tabelle_3(daten_tabelle_3.Seq ) := daten_tabelle_3; end loop; close csr_3; end; procedure AufbauVerlaufsdaten ( verlauf IN OUT NOCOPY quelldaten_hash ) is Vorgaenger quelldaten_type; Nachfolger quelldaten_type; VorgaengerExistiert boolean; NachfolgerExistiert boolean; i number; begin -- Source-Code siehe weiter unten im Text end; end verlauftest;

Für den Cursor csr_1 sollte beachtet werden, dass das entsprechende SQL-Statement mit einem order by formuliert wird: gibt es in der Quelltabelle Tabelle_1 für unsere gewählte Entität mehrere Ereignisse für das gleiche Startdatum, dann wird in diesem Fall nur der zuletzt selektierte Datensatz in unsere Verlaufsdatenstruktur aufgenommen (alle anderen

zuvor werden überschrieben). Ein Beispiel hierfür wäre etwa, dass das Verlaufsdatenmodell lediglich den letzten Produktwechsel eines Tages berücksichtigen soll. Für den Cursor csr_2 gilt entsprechendes, nur mit dem Unterschied, dass die Quelldaten aus einer View bereit gestellt werden und daher die Verlaufsdatenstruktur für Tabelle_2 der Struktur der View entsprechen muss. Für den Cursor csr_3 liegt aber der Fall vor, dass es für unsere Entität mehrere Ereignisse für ein Startdatum geben kann, jedoch zum Zeitpunkt der Selektion noch nicht entschieden werden kann, welcher der Datensätze übernommen wird. Ein Beispiel hierfür wäre etwa, dass Tabelle_3 als Basis für den Cursor csr_3 Kündigungsrücknahmen enthält, die aber nur dann verarbeitet werden sollen, wenn sich diese auf bestimmte Tarife beziehen, die etwa über die Tabelle_1 selektiert werden können. In manchen Fällen soll für das Verlaufsdatenmodell ein Mapping durchgeführt werden, etwa dass eine Produkt-ID als Produkt-Name dargestellt werden soll. Es sollte vermieden werden, durch entsprechende Selects die (einzelnen) Mappingdaten zu bestimmen: effizienter wird es sein, Mapping-Tabellen als Hash initial einmal zu laden und dann im Programmverlauf nur noch den Hash entsprechend zu befragen. Dieser Ansatz lohnt sich insbesondere dann, wenn Massendaten im Verlaufsdatenmodell verarbeitet werden sollen.

Aufbau des Regelwerks Der Aufbau des Regelwerks ist der eigentliche Kern und bedarf daher der besonderen Aufmerksamkeit. Keinesfalls sollte man davon ausgehen, dass die Quelldaten inhaltlich stets korrekt vorliegen. Mitunter könnten ganze Ereignisketten fehlen, als Folge eines Löschvorganges oder einer (gewollt) unvollständigen Migration ähnlicher Daten aus anderen Systemen, etc. Es wird erforderlich sein, das Regelwerk iterativ aufzubauen. Mit dem Aufbau des Regelwerks sollte auch gleich ein separates Modul geschrieben werden, dass die verarbeiteten Daten auf Korrektheit im Verlaufsdatenmodell überprüft. Die Iteration verläuft nun so, dass mit einer bestimmten Bedingung/Regel Datensätze verarbeitet werden. Die Prüfsoftware ermittelt Unstimmigkeiten, die als Folge dazu führen, dass die Bedingungen erweitert / verfeinert werden müssen. Unter Umständen entstehen auf diese Weise gewaltige IF THEN ELSE-Verschachtelungen, die sorgfältig in allen ihren Einzelheiten dokumentiert werden sollten. Es kann auch vorkommen, dass eine bereits entwickelte Bedingung an einer anderen Stelle im Source-Code wiederverwendet werden muss – daher empfiehlt es sich, Bedingungen als Funktionen auszulagern. Zum Aufbau des Regelwerks sind beispielhaft die folgenden Punkte zu berücksichtigen: -

gibt es Ereignisse, deren Hintereinanderausführungen sich gegenseitig aufheben? o Beispiel: Aktivierung / Deaktivierung am gleichen Tag

-

gibt es Ereignisse, deren Hintereinanderausführung sich widersprechen? o Beispiel: Produktwechsel nach Deaktivierung gibt es mehrere Ereignisse für den gleichen Tag? o Beispiel: Aktivierung und Produktwechsel am gleichen Tag gibt es Ereignisse, die "nur für einen Tag" gelten? o Beispiel: mehrere Produktwechsel an einem Tag

-

Für ähnliche Fälle sollte geklärt werden, welche Daten wie in das Laufzeitdatenmodell einfließen sollen. Implementierung des Verlaufdatenmodells In einer Schleife über den alpha-numerisch sortierten Index des Datenhashes werden nun alle Datensätze verarbeitet. Man beachte im nachfolgenden Beispiel-Code, dass Daten sowohl vom "aktuellen" Index wie auch des Vorgängers verwendet und auch zurückgeschrieben werden. Hierbei zeigt sich der Vorteil, dass alle benötigten Daten in einer gemeinsamen Struktur vorliegen: alle Daten stehen ohne erneuten Zugriff auf die Datenbank zur Verfügung.

procedure AufbauVerlaufsdaten ( verlauf IN OUT NOCOPY quelldaten_hash ) is Vorgaenger quelldaten_type; VorgaengerExistiert boolean; VorgaengerIndex number; i number; begin i := verlauf.FIRST; VorgaengerExistisrt := FALSE; while ( verlauf.Exist(i) ) loop -- wenn es bereits in der Zieltabelle einen Vorgänger gibt, dann -- übernehme zunächst alle Daten des Vorgängers, da sich nur wenige -- Felder ändern werden. if ( VorgaengerExistiert ) then verlauf(i).zieltabelle := Vorgaenger.zieltabelle; end if; -----

Einige Spalten können bereits jetzt gesetzt werden: - das Startdatum kann gesetzt werden - das EndeDatum kann zunächst auf NULL gesetzt werden - das AKTUELL-Feld kann auf "Ja" gesetzt werden

verlauf(i).zieltabelle.start_datum := to_date('01.01.1960', 'dd.mm.yyyy' ) + i; verlauf(i).zieltabelle.ende_datum := NULL; verlauf(i).zieltabelle.aktuell := 'Ja'; if ( verlauf.Exists(verlauf.Prior(i) ) then -- Setze den Vorgänger VorgaengerIndex

:= verlauf.Prior(i);

Vorgaenger := verlauf( VorgaengerIndex ); VorgaengerExistiert := TRUE; -- vom Vorgänger können jetzt ebenfalls einige Spalten richtig -- gesetzt werden: verlauf( VorgaengerIndex ).zieltabelle.ende_datum := verlauf(i).zieltabelle.start_datum – 1; verlauf( VorgaengerIndex ).zieltabelle.aktuell = 'Nein'; end if; -- Definition des Regelwerks … if ( !IstPrepaidKunde( vorgaenger ) and !IstPrepaidKunde( verlauf(i) ) and IstKundenwechsel ( vorgaenger, verlauf(i) ) then verlauf(i).zieltabelle.kundenwechsel := TRUE; verlauf(i).zieltabelle.alte_kunden_nr := vorgaenger.col3; verlauf(i).zieltabelle.kunden_nr := verlauf(i).tabelle_1.col3; verlauf(i).zieltabelle.kunde_seit := i; verlauf(i).zieltabelle.prepaid_kunde := TRUE; end if; --

… weitere

Plausibilitäten / Bedingungen / Berechnungen

-- Dieses Statement muss das letzte in der While-Schleife sein i := verlauf.Next(i); end loop; end;

Der Beispiel-Code zeigt bereits, wie bestimmte Felder des Laufzeitdatenmodells gesetzt werden können. Der Zielhash sollte Stück für Stück aufgebaut werden: in der Regel kann man für ein bestimmtes Startdatum zunächst die Daten des Vorgängers vom Zielhash kopieren, da sich nur wenige Datenfelder ändern werden. Implementierung in anderen Programmiersprachen Mittlerweile werden Assoziative Arrays in allen gängigen Programmiersprachen (C++ [ jedoch nicht C ], Java) und Skriptsprachen ( Perl, Ruby, Javascript ) unterstützt. Mit der beschriebenen Vorgehensweise lässt sich daher auch in den genannten Programmiersprachen das Verlaufsdatenmodell fast identisch entwickeln. Der eigentliche Vorteil bei der Programmierung in PL/SQL ergibt sich aus dem einfacheren Aufbau der Datenstrukturen, da diese mittels %rowtype direkt aus der Tabellenstruktur übernommen werden kann. Veränderungen am Datenmodell können damit leichter verarbeitet werden. In anderen Programmiersprachen sieht dies anders aus: alle Datenstrukturen müssen auf Feld-Ebene manuell aufgebaut und programmiert werden, Wertzuweisungen oder Vergleiche zusammengesetzter Datenstrukturen müssen meist atomar aufgeschlüsselt werden. Unter Umständen wartet hier erhebliche Tipparbeit. Gerade diese Arbeit birgt die Gefahr,

durch Schreibfehler bei den Schlüsseln der Hashes nur schwer erkennbare Datenfehler zu produzieren. Die eigentliche Herausforderung bei der Erstellung des Laufzeitdatenmodells wird sicher in der Implementierung des Regelwerks liegen. Es empfiehlt sich daher ein Debugger, der in einfacher Weise verschachtelte Datenstrukturen anzeigen kann. Nicht alle Programmiersprachen verfügen über einen solchen Debugger.

Kontaktadresse: Dipl.-Math. Hans-Friedrich Pfeiffer Talklineplatz 1 D-25388 Elmshorn Telefon: Fax: E-Mail Internet:

+49(0)4121-41-2453 +49(0)4121-41-43-2453 [email protected] http://www.talkline.de