Zeitreihen einmal anders

Beispiele für Zeitreihen, -vergleiche und -analysen kennen DeltaMaster-Anwender zur Genüge: Wie hat sich der Umsatz innerhalb der letzten 12 Monate entwickelt? Wie war die durchschnittliche Quartalsrendite der vergangenen drei Jahre? Welche Höhen und Tiefen hat der Lagerbestand im aktuellen Geschäftsjahr erfahren? Alle voranstehenden haben aus der Distanz, d.h. technisch abstrahiert, eines gemeinsam: Sie zeigen atomare Werte oder verwenden darauf übliche Aggregatfunktionen (Summe, Durchschnitt, Minimum/Maximum) für einen klar definierten und vor allem ununterbrochenen Zeitraum (z.B. die letzten n Perioden, alle Perioden innerhalb eines Knotens).

Eine scheinbar nur leichte Variation, bei genauerer Betrachtung aber völlig unterschiedliche Logik erfordert die folgende Fragestellung, die vielen Lesern weniger aus der Welt der Geschäftsdaten bekannt sein dürfte, durchaus aber aus dem Privatleben: “Dies ist der heißeste Juli der letzten 30 Jahre”, heißt es in der Wettervorhersage, oder “Noch nie seit Beginn der Wetteraufzeichnung gab es in Nürnberg so wenig Niederschlag wie in diesem Frühjahr”. Aber auch Aussagen wie “Noch an keinem Freitag konnten wir in diesem Jahr so viele Kunden begrüßen wie letzte Woche” folgen derselben Logik: Hier werden nämlich keine geschlossenen Zeitreihen gebildet, sondern vielmehr aus einer längeren Reihe oder gar allen Perioden nur diejenigen in eine Darstellung oder Berechnung einbezogen, die bestimmte Ausprägungen haben: “Nehme alle Tage dieses Jahres, filtere daraus die Samstage und ermittle dann diejenige mit der maximalen Anzahl Kunden” lautet das letzte Beispiel übersetzt.

Diese scheinbar einfachen Sachverhalte mit MDX-Bordmitteln effizient umzusetzen ist keine leichte Aufgabe. Dieser Beitrag skizziert einige Lösungsansätze.

  1. Statische Lösung

Das folgende Beispiel bildet ein 10-Jahres-Mittel als berechnetes Element innerhalb der Dimension “Kumulation” und ist direkt übertragbar in alle Modeler-basierten Datenmodelle:

Avg(
       {
         (
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,0
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
      ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,2
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
         ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,3
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
         ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,4
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
         ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,5
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
         ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,6
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
         ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,7
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
         ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,8
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
         ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,9
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )
    }
)   ,(
              ParallelPeriod(
            [Zeit].[Zeit].[Jahr]
            ,1
            ,[Zeit].[Zeit].CurrentMember
             )
            , [Kumulation].[Kumulation].&[1]
        )

Der Vorteil dieser Lösung liegt in ihrer universellen Anwendbarkeit, der Nachteil selbstredend in der statischen Implementierung.

2. Dynamische Ansätze

Das folgende Beispiel erzeugt wiederum ein berechnetes Element in der Dimension “Kumulation”. Der DeltaMaster-Platzhalter “view2″ entspricht im Beispiel der Dimension “Periode” und ist entsprechend anzupassen:

Avg
(
  Generate
  (
    {[Periode].[Periode].[Jahr].MEMBERS}
   ,{
      Cousin
      (
        <view2>
       ,[Periode].[Periode].CurrentMember
      )
    }
  )
 ,[Kumulation].[Kumulation].[Kumulation].&[1]
)

Zweifelsfrei ist diese Variante deutlich kompakter, hat allerdings den gravierenden Nachteil, dass sie aufgrund der Verwendung von Generate, ViewX und CurrentMember nicht funktioniert, wenn die Periodendimension auf einer Achse verwendet wird.

Abschließend noch ein alternativer dynamischer Ansatz aus einem älteren Beitrag auf der Basis der Microsoft-Standarddemo “Adventure Works”, der eine universell einsetzbare Kennzahl bildet, die für jeden Monat den jahresübergreifenden Durchschnitt des Auftragseingangs berechnet. Eine in unseren Datenmodellen üblicherweise unerfüllte Voraussetzung hierfür ist allerdings die Existenz einer zusätzlichen, ggf. unsichtbaren Hierarchie MonatOhneJahr (“Month of Year”) innerhalb der Dimension “Periode”.

WITH
  MEMBER [Measures].[AvgMonthOrders] AS
    Avg
    (
      Exists
      (
        [Date].[Calendar].[Month].MEMBERS
       ,(EXISTING
          [Date].[Month of Year].MEMBERS)
      )
     ,[Measures].[Order Quantity]
    )
SELECT
  {
    [Measures].[Order Quantity]
   ,[Measures].[AvgMonthOrders]
  } ON 0
 ,[Date].[Calendar].[Month].MEMBERS ON 1
FROM [Adventure Works];

Wir arbeiten aktuell an weiteren Varianten. Eine generische Implementierung in DeltaMaster als Zeitanalyseelement ist geplant. Bis dahin sind Kommentare und Optimierungsvorschläge wie immer herzlich willkommen!