Jörg Menker

Als ich vor kurzem bei einem Kunden einen Workshop zum Thema Datenmodellierung und Datenbewirtschaftung mit SSIS durchführte, entwickelten wir ein kleines Star-Schema und bewirtschafteten die Dimensions- und Faktentabellen. Dabei ließ ich den Kunden alles selbst machen und beschränkte mich auf die Rolle des Trainers. Das klappte auf Anhieb sehr gut, und schnell war das Star-Schema mit Daten gefüllt, die schon produktiv ausgewertet werden konnten. Dabei kam der Kunde auf den Geschmack und wollte das Modell um zusätzliche Informationen, die sich in Form neuer Attribute in den Dimensionstabellen niederschlugen, anreichern. Das Datenmodell entsprechend zu erweitern und die Änderungen auf der Datenbank nachzuziehen war schnell erledigt und auch die nötige Anpassung der SSIS-Packages, mit denen die Zieltabellen bewirtschaftet wurden, war kein Problem, und die Daten konnten neu geladen werden. Dabei stellte sich aber heraus, dass nun mehr Fakten geladen wurden als vorher, ohne dass sich an den zugrundeliegenden Quelldaten etwas geändert hatte.

Die Ursache war schnell gefunden: Wir hatten ein kartesisches Produkt erzeugt, indem wir eine neue Tabelle hinzugejoint hatten!

Diese neue Tabelle enthielt die zusätzlichen Attribute. Die benötigten Informationen konnten jedoch in zwei Spalten der neuen Tabelle zu finden sein, was unproblematisch gewesen wäre, wenn sich die Spalten nicht auch in unterschiedlichen Zeilen befunden hätten, denn es galt folgende Mimik:

Wenn Spalte 1 (MM1) in Zeile 1 gefüllt ist, dann soll der Wert aus Spalte 1 (MM1) verwendet werden, ansonsten soll der Wert aus Spalte 2 (MM2) der Zeile 2 verwendet werden. Es ist immer nur MM1 oder MM2 gefüllt und nicht für jeden Wert (Join-Spalte) gibt es zwingend auch zwei Zeilen.

Die möglichen 2 Zeilen waren der Grund dafür, dass wir kartesisch geworden waren, denn die obige Mimik wurde uns erst klar als wir uns die neue Tabelle näher angeschaut hatten. Wir standen also vor der Herausforderung den richtigen Wert aus MM1/Zeile 1 oder MM2/Zeile 2 zu ermitteln ohne kartesisch zu werden.

Um den Sachverhalt an einem einfachen Beispiel zu illustrieren erzeugen wir uns eine kleine Beispieltabelle:

In diese Tabelle stellen wir nun ein paar Datensätze ein, die die Problematik abbilden:

 

Jetzt sieht unsere Beispieltabelle etwa so aus:

Ausgangstabelle

Die Spalte xyz_id ist die Spalte, über die der Join erfolgen soll. Für jeden Wert von xyz_id (Joinspalte) soll nur eine Zeile zurückkommen.

Nach der o.a. Mimik würden wir also folgendes Ergebnis erwarten:

Wert
1 Y
2 X
3 Z

Diese Ergebnis erhalten wir, indem wir prüfen ob MM1 oder MM2 den Wert NULL aufweist und den jeweils gefüllten Wert (not null) auswählen und nach xyz_id gruppieren. Den selektierten Wert müssen wir mit einer Aggregatfunktion (z.B. MIN() oder MAX())behandeln, damit wir tatsächlich nur einen Satz für jede Ausprägung von xyz_id bekommen:

Die Anwendung von MAX() führt in diesem einfachen Beispiel – zufällig – zum richtigen Ergebnis (Spalte xyz_max):

Zwischenergebnis

Aber auf den Zufall kann man sich bekanntlich nicht verlassen. Daher benötigen wir ein Verfahren, mit dem es immer klappt. Dazu verwenden wir einen kleinen Trick und konkatenieren 1 vor den Wert, wenn er aus MM1 stammt und 2, wenn er aus MM2 stammt. In Verbindung mit MIN() erhalten wir so immer den richtigen Wert:

Die 1 bzw. 2 entfernt man dann im Nachgang einfach wieder und schon kann man ohne das Risiko, ein kartesisches Produkt zu erzeugen, das Ergebnis des obigen SQL-Statements in einem Join verwenden.

Enderegbnis