Suchen...
Generic filters
Exact matches only
Search in title
Search in excerpt
Search in content

Grünes Licht für mehrdimensionales Ranking mit TM1

Für Auswertungen benötigen wir oft mehrdimensionale Rankings. Eine typische Anforderung aus dem Logistikbereich wäre beispielsweise, die Top-10 Strecken mit der größten Transportmenge zu identifizieren. Hierzu müssen alle Kombinationen aus Verlade- und Entladestationen im Sinne einer Transportmatrix ermittelt und in eine Rangfolge gebracht werden. In DeltaMaster findet sich das Analysemodul PowerSearch, das genau für derartige Auswertungen gemacht ist. Heißt die zugrunde liegende Datenbank aber TM1, dann währt die Freude über den vorhandenen Standard nicht lange. Denn bereits ab einer kleinen Datendosis lässt PowerSearch auf sich warten und dem TM1 Server geht die Puste aus. Was im SQL-Server-Umfeld gängige Praxis ist, wird bei TM1 plötzlich zum Problem. Worin liegt die Ursache? Lässt es sich beheben? Die Antwort finden Sie in folgendem Beitrag.

Ursachenforschung

DeltaMaster generiert MDX-Statements, die über die OLEDB-Schnittstelle an den TM/1 Server geschickt werden. Leider unterstützt diese nicht alle MDX-Funktionen, die wir aus der Microsoftwelt kennen. Besonders schmerzlich vermissen wir die NonEmptyCrossjoin-Funktion, mit der Objektkombinationen aus mehreren Dimensionen performant ermittelt werden. Für die Performance sorgt die NonEmpty-Bedingung: durch sie werden leere Tupel und Tupel ohne zugeordnete Daten einer Faktentabelle ausgeschlossen.

Um das Fehlen der NonEmptyCrossjoin-Funktion zu kompensieren, generiert PowerSearch eine MDX-Abfrage unter Verwendung eines „einfachen“ Crossjoin in Kombination mit Filter-Funktionen. Der Crossjoin führt eine Mengenoperation aus, die das Kreuzprodukt zweier Mengen zurückgibt. Durch die zusätzlichen Filter werden nur diejenigen Member aus den zu kombinierenden Dimensionen in den Crossjoin übergeben, die eine im Filter verankerte Bedingung erfüllen. In aller Regel wird geprüft, ob ein Member im Hinblick auf die zu analysierende Kennzahl einen Wert ungleich 0 besitzt. Durch das Filtern leerer Member aus den Mengen reduziert sich auch der Umfang des Kreuzproduktes, das mit dem Crossjoin aus eben diesen Mengen generiert wird:

CROSSJOIN(

           Filter({DIM_1.members}, [filtermeasure] <> 0),
           Filter({DIM_2.members}, [filtermeasure] <> 0)

)

Ein Crossjoin mit Filter-Funktionen arbeitet jedoch viel langsamer als ein NonEmptyCrossjoin, weswegen dieses Abfragekonstrukt schnell zu Performanceproblemen führt. Ein entscheidender Grund hierfür ist, dass im Vergleich zur NonEmptyCrossjoin-Funktion keine Tupel, sondern nur die Elemente der jeweiligen Dimensionen unabhängig voneinander auf Werte geprüft werden. Dies bereinigt zwar die jeweiligen Mengen um leere Elemente, nicht aber das Kreuzprodukt um leere Tupel. Letztere sind zu allem Überfluss meist sehr zahlreich vorhanden. Denn – um bei unserem Beispiel zu bleiben – nicht jede Verladestation liefert zu jeder Entladestation.

Lösung

Wir müssen uns daher einer MDX-Funktion bedienen, mit der sich die Größe der Objektmengen für den Crossjoin noch effektiver einschränken lässt. Bevor wir unsere Wahl treffen sind diesbezüglich einige Hypothesen anzustellen. Bleiben wir beim Beispiel der Transportstrecken. Angenommen es gäbe je 5000 Start und Zielpunkte, dann wäre zu vermuten, dass sich die 10 größten Strecken mit relativ hoher Wahrscheinlichkeit zwischen den 500 größten (TopCount500) Verlade- und den 500 größten Entladestationen befinden. Somit würden wir bei der Berechnung des Kreuzproduktes keine 5.000 * 5.000 = 25.000.000 Tupel ermitteln, sondern lediglich 500 * 500 = 25.000. Das ist ein Anteil von nur 1% gegenüber der Größe des ursprünglichen Kreuzproduktes. Wir haben mit dem TopCount einen riesigen Hebel, um die Anzahl der Berechnungen zu reduzieren und so die Performance des Crossjoin zu verbessern:

TopCount(
          CROSSJOIN(
               TopCount({DIM_1.members}, 500, [Menge]),
               TopCount({DIM_2.members}, 500, [Menge])),
          10,
          [Menge]
)

Die Herausforderung bei dieser Vorgehensweise liegt in der Wahl der „richtigen“ Anzahl zu betrachtender Objekte im TopCount.

Kritische Anmerkung

Der Performancegewinn beim mehrdimensionalen Ranking, der durch die oben beschriebene Vorgehensweise erzielt wird, spricht für sich. Allerdings besteht immer die Gefahr, dass durch das radikale Streichen von Objekten vereinzelte Sonderfälle unberücksichtigt bleiben. Hierzu ein Beispiel: Große Verladestationen liefern stets kleine Mengen an viele andere für sich betrachtet große Entladestationen. Die Strecken sind zahlreich, die transportierten Mengen aber eher klein. Daneben gibt es noch eine kleine Station A (an Position 600 im TopCount nach Menge). Diese liefert ausschließlich an eine andere kleine Station B, die auch nur von Station A beliefert wird. Somit wird eine vergleichsweise große Menge auf einer Strecke zwischen 2 vergleichsweise kleinen Stationen transportiert. Würde eine dieser Stationen durch das Raster des TopCount fallen, so würde auch die gesamte Strecke (als Tupel aus Station A und B) nicht berücksichtigt.

Daher empfiehlt es sich stets, die Einstellung des TopCounts in Abstimmung mit dem Experten auf Kundeseite zu justieren und zahlreiche Tests durchzuführen. So lange, bis die hoffentlich richtige Einstellung getroffen ist.

Ausblick

Im obigen Beispiel der Transportstrecken sind wir immer von einem Crossjoin zweier Dimensionen ausgegangen. Selbstverständlich lässt sich unser TopCount-Turbo-Kniff auf die gleich Weise auch auf eine höhere Anzahl zu kombinierender Dimensionen anwenden. Hierbei ist zu beachten, dass sich in TM1 nicht mehr als 2 Dimensionen in einer Crossjoin-Funktion verwenden lassen. Bei der Berechnung des Kreuzproduktes aus mehr als 2 Dimensionen müssen stattdessen mehrere Crossjoins wie folgt ineinander geschachtelt werden:

TopCount(
          CROSSJOIN(
                    TopCount({DIM_1.members}, 500, [Menge]),
                    CROSSJOIN(
                               TopCount({DIM_2.members}, 500, [Menge]),
                               TopCount({DIM_3.members}, 500, [Menge]))
                   ),
          10,
          [Menge]
         )