Time Intelligence (deklarativ: YoY, PoP, YTD, Rolling)
Mondrian-4 unterstützt ein <TimeCalc>-Schema-Element, das gängige Time-Intelligence-Metriken deklariert. Der Schema-Loader entzuckert jede Deklaration in einen validierten <CalculatedMember> auf [Measures] – Sie sagen also was Sie möchten, statt MDX-Formeln von Hand zu schreiben und zu pflegen.
Warum deklarative Time Intelligence?
Ohne <TimeCalc> erfordert Year-over-Year-Wachstum einen berechneten Member wie:
<CalculatedMember name="Revenue YoY" dimension="Measures"> <Formula> ([Measures].[Revenue] - (ParallelPeriod([Calendar].[Year], 1, [Calendar].CurrentMember), [Measures].[Revenue])) / (ParallelPeriod([Calendar].[Year], 1, [Calendar].CurrentMember), [Measures].[Revenue]) </Formula> <CalculatedMemberProperty name="FORMAT_STRING" value="0.0%"/></CalculatedMember>Mit <TimeCalc> ist dieselbe Metrik:
<TimeCalc name="Revenue YoY" type="yoy" measure="Revenue" timeDimension="Calendar" formatString="0.0%"/>Der Loader erzeugt das MDX für Sie, validiert, dass die referenzierte Measure und Zeitdimension existieren, und wirft einen Ladezeitfehler, statt ein still falsches Ergebnis zu produzieren.
Voraussetzung: eine getypte Time-Dimension
<TimeCalc> erfordert, dass der Cube eine getypte Time-Dimension hat – eine <Dimension> mit type="TIME", deren Hierarchie benannte Ebenen für Jahr, Quartal und Monat hat. Die Jahresebene muss levelType="TimeYears" tragen, und die Quartals- und Monatsebenen müssen levelType="TimeQuarters" bzw. levelType="TimeMonths" tragen. Die innerhalb-eines-Jahres-Berechnungen (ytd, pop, rolling) erfordern mindestens eine Monatsebene in der Hierarchie.
Eine minimale Calendar-Dimension, die die Anforderung erfüllt:
<Dimension name="Calendar" type="TIME" table="dim_date" key="Date"> <Attributes> <Attribute name="Year" keyColumn="year_num" levelType="TimeYears"/> <Attribute name="Quarter" keyColumn="quarter_key" levelType="TimeQuarters"/> <Attribute name="Month" keyColumn="month_key" levelType="TimeMonths"/> <Attribute name="Date" keyColumn="date_key" levelType="TimeDays"/> </Attributes> <Hierarchies> <Hierarchy name="Calendar" allMemberName="All Time"> <Level attribute="Year"/> <Level attribute="Quarter"/> <Level attribute="Month"/> <Level attribute="Date"/> </Hierarchy> </Hierarchies></Dimension>Calendar: type: "TIME" table: "dim_date" key: "Date" attributes: - name: "Year" key_column: "year_num" level_type: "TimeYears" - name: "Quarter" key_column: "quarter_key" level_type: "TimeQuarters" - name: "Month" key_column: "month_key" level_type: "TimeMonths" - name: "Date" key_column: "date_key" level_type: "TimeDays" hierarchies: - name: "Calendar" all_member_name: "All Time" levels: - attribute: "Year" - attribute: "Quarter" - attribute: "Month" - attribute: "Date"Platzierung im Schema
<TimeCalc>-Elemente werden in einem <TimeCalcs>-Block innerhalb eines <Cube> auf derselben Ebene wie <CalculatedMembers> eingeschlossen:
<Cube name="Monthly Revenue">
<Dimensions> <Dimension source="Calendar"/> <!-- other dimensions --> </Dimensions>
<MeasureGroups> <MeasureGroup name="Revenue" table="monthly_revenue_fact"> <Measures> <Measure name="Revenue" column="revenue" aggregator="sum"/> </Measures> <DimensionLinks> <ForeignKeyLink dimension="Calendar" foreignKeyColumn="month_key"/> </DimensionLinks> </MeasureGroup> </MeasureGroups>
<TimeCalcs> <TimeCalc name="Revenue YoY" type="yoy" measure="Revenue" timeDimension="Calendar" formatString="0.0%"/> <TimeCalc name="Revenue PoP" type="pop" measure="Revenue" timeDimension="Calendar" formatString="0.0%"/> <TimeCalc name="Revenue YTD" type="ytd" measure="Revenue" timeDimension="Calendar"/> <TimeCalc name="Revenue R3" type="rolling" measure="Revenue" timeDimension="Calendar" window="3" function="avg"/> </TimeCalcs>
</Cube>Monthly Revenue: dimensions: - source: "Calendar" measure_groups: - name: "Revenue" table: "monthly_revenue_fact" measures: - name: "Revenue" column: "revenue" aggregator: "sum" dimension_links: - type: "foreign_key" dimension: "Calendar" foreign_key_column: "month_key" time_calcs: - name: "Revenue YoY" type: "yoy" measure: "Revenue" time_dimension: "Calendar" format_string: "0.0%" - name: "Revenue PoP" type: "pop" measure: "Revenue" time_dimension: "Calendar" format_string: "0.0%" - name: "Revenue YTD" type: "ytd" measure: "Revenue" time_dimension: "Calendar" - name: "Revenue R3" type: "rolling" measure: "Revenue" time_dimension: "Calendar" window: 3 function: "avg"Attributreferenz
| Attribut | XML / YAML-Schlüssel | Erforderlich | Beschreibung |
|---|---|---|---|
name | name | ja | Der Name des erzeugten berechneten Members. Erscheint in [Measures] wie jede andere Measure. |
type | type | ja | Der Metriktyp: yoy, pop, ytd oder rolling. Siehe Metriktypen unten. |
measure | measure | ja | Der Name einer existierenden <Measure> im Cube. Der Loader lehnt eine unbekannte Measure beim Schema-Load ab. |
timeDimension | time_dimension | bedingt | Der Name einer type="TIME"-Dimension. Kann weggelassen werden, wenn der Cube genau eine TIME-Dimension hat; erforderlich, wenn er mehr als eine hat. |
window | window | nur rolling | Ganzzahlige Anzahl der Perioden, die in das rollende Fenster einbezogen werden. |
function | function | nur rolling | Aggregationsfunktion über das Fenster: sum (Standard) oder avg. |
formatString | format_string | nein | MDX-Format-String, der auf den erzeugten Member angewendet wird, z. B. "0.0%" oder "#,###". |
Metriktypen
yoy – Year-over-Year-Wachstum
Berichtet die prozentuale Änderung im Vergleich zur gleichen Periode im Vorjahr.
Formelform:
([Measures].[<measure>] - (ParallelPeriod([<dim>].[<YearLevel>], 1, [<dim>].CurrentMember), [Measures].[<measure>]))/(ParallelPeriod([<dim>].[<YearLevel>], 1, [<dim>].CurrentMember), [Measures].[<measure>])Der ParallelPeriod-Aufruf navigiert mit der TimeYears-Ebene zur gleichen relativen Position ein Jahr zurück. Das Ergebnis ist NULL für das erste volle Datenjahr (kein Vorjahr verfügbar).
pop – Period-over-Period-Wachstum
Berichtet die prozentuale Änderung im Vergleich zur unmittelbar vorhergehenden Periode (die Periode direkt vor der aktuellen auf derselben Ebene).
Formelform:
([Measures].[<measure>] - (PrevMember([<dim>].CurrentMember), [Measures].[<measure>]))/(PrevMember([<dim>].CurrentMember), [Measures].[<measure>])PrevMember geht eine Position in der natürlichen Ordnung der Dimension zurück. Ergebnisse sind NULL für den allerersten Member einer Ebene (kein Vorgänger).
ytd – Year-to-Date kumulativ
Berichtet den kumulierten Wert der Measure vom Anfang des aktuellen Jahres bis zur aktuellen Periode.
Formelform:
Aggregate(Ytd([<dim>].CurrentMember), [Measures].[<measure>])Ytd() gibt die Menge aller Perioden von der ersten Periode des aktuellen Jahres bis zur aktuellen Periode zurück. Aggregate wendet die native Aggregation der Measure (typischerweise sum) über diese Menge an.
rolling – Rolling Window
Berichtet das Aggregat der Measure über die letzten window Perioden, unter Verwendung von sum oder avg.
Formelform (avg, window=3):
Avg(LastPeriods(3, [<dim>].CurrentMember), [Measures].[<measure>])Formelform (sum, window=N):
Sum(LastPeriods(N, [<dim>].CurrentMember), [Measures].[<measure>])LastPeriods(N, member) gibt die Menge der N Perioden zurück, die am aktuellen Member enden. Wenn weniger als N Perioden verfügbar sind (z. B. früh in der Datenhistorie), schrumpft das Fenster auf die Anzahl tatsächlich existierender Perioden – es wird nicht mit Nullen aufgefüllt.
Validierungsverhalten
Der Loader ist fail-closed: Das Laden des Schemas wird mit einer klaren Fehlermeldung abgebrochen, wenn eine der folgenden Bedingungen erkannt wird.
| Bedingung | Fehler |
|---|---|
measure benennt einen Member, der nicht im Cube existiert | TimeCalc "X": measure "Y" not found in cube |
Keine timeDimension angegeben und der Cube hat null TIME-Dimensionen | TimeCalc "X": cube has no TIME dimension |
Keine timeDimension angegeben und der Cube hat mehr als eine TIME-Dimension | TimeCalc "X": cube has multiple TIME dimensions — specify timeDimension |
timeDimension benennt eine Dimension, die nicht im Cube existiert | TimeCalc "X": timeDimension "Y" not found |
type="rolling" und window fehlt oder ist keine positive Ganzzahl | TimeCalc "X": rolling type requires a positive integer window |
type="yoy", aber keine TimeYears-Ebene existiert in der Dimension | TimeCalc "X": TIME dimension has no TimeYears level |
Es gibt kein still falsches Ergebnis – jede Fehlkonfiguration wird vor der ersten Abfrage erkannt.
Beispiel: Monatlicher Umsatz aus der Bank-Demo
Die Bank-Demo liefert einen Monthly Revenue-Cube über eine monatliche Umsatzreihe. Die Rohdaten für zwei Jahre:
| Year | Month | Revenue |
|---|---|---|
| 2024 | Jan | 100 |
| 2024 | Feb | 200 |
| 2024 | Mar | 300 |
| 2025 | Jan | 150 |
| 2025 | Feb | 250 |
| 2025 | Mar | 350 |
Der Cube deklariert alle vier <TimeCalc>-Typen gegen die Calendar-Dimension (Year > Quarter > Month):
<TimeCalcs> <TimeCalc name="Revenue YoY" type="yoy" measure="Revenue" timeDimension="Calendar" formatString="0.0%"/> <TimeCalc name="Revenue PoP" type="pop" measure="Revenue" timeDimension="Calendar" formatString="0.0%"/> <TimeCalc name="Revenue YTD" type="ytd" measure="Revenue" timeDimension="Calendar"/> <TimeCalc name="Revenue R3" type="rolling" measure="Revenue" timeDimension="Calendar" window="3" function="avg"/></TimeCalcs>time_calcs:- name: "Revenue YoY" type: "yoy" measure: "Revenue" time_dimension: "Calendar" format_string: "0.0%"- name: "Revenue PoP" type: "pop" measure: "Revenue" time_dimension: "Calendar" format_string: "0.0%"- name: "Revenue YTD" type: "ytd" measure: "Revenue" time_dimension: "Calendar"- name: "Revenue R3" type: "rolling" measure: "Revenue" time_dimension: "Calendar" window: 3 function: "avg"Erwartete Ergebnisse
| Zelle | Wert | Wie |
|---|---|---|
| Revenue YoY bei [Calendar].[2025].[Q1].[Jan 2025] | 0.5 (50%) | (150 − 100) / 100 = 0.5 |
| Revenue PoP bei [Calendar].[2024].[Q1].[Feb 2024] | 1.0 (100%) | (200 − 100) / 100 = 1.0 |
| Revenue YTD bei [Calendar].[2024].[Q1].[Mar 2024] | 600 | 100 + 200 + 300 = 600 |
| Revenue R3 bei [Calendar].[2025].[Q1].[Mar 2025] | 250 | avg(150, 250, 350) = 250 |
Beispiel-MDX-Abfrage
SELECT { [Measures].[Revenue], [Measures].[Revenue YoY], [Measures].[Revenue PoP], [Measures].[Revenue YTD], [Measures].[Revenue R3] } ON COLUMNS, [Calendar].[Month].Members ON ROWSFROM [Monthly Revenue]Teilergebnis (2024–2025, Januar bis März):
| Month | Revenue | YoY | PoP | YTD | R3 |
|---|---|---|---|---|---|
| Jan 2024 | 100 | — | — | 100 | 100 |
| Feb 2024 | 200 | — | 100.0% | 300 | 150 |
| Mar 2024 | 300 | — | 50.0% | 600 | 200 |
| Jan 2025 | 150 | 50.0% | −50.0% | 150 | 216.7 |
| Feb 2025 | 250 | 25.0% | 66.7% | 400 | 233.3 |
| Mar 2025 | 350 | 16.7% | 40.0% | 750 | 250 |
Bindestriche (—) zeigen NULL an – keine Vorjahresdaten oder keine Vorgängerperiode verfügbar.
Beziehung zu <CalculatedMembers>
<TimeCalc>-Deklarationen werden zur Ladezeit zu <CalculatedMember>-Elementen auf [Measures] entzuckert. Die erzeugten Members sind zur Abfragezeit von handgeschriebenen berechneten Members nicht zu unterscheiden: Sie erscheinen in XMLA-Member-Enumerationen, reagieren auf FORMAT_STRING und können von anderen berechneten Members referenziert werden.
Wenn Sie eine Formel benötigen, die <TimeCalc> nicht ausdrücken kann – z. B. eine benutzerdefinierte gemischte Metrik oder ein Multi-Measure-Verhältnis vor einem Zeitvergleich – verwenden Sie direkt einen einfachen <CalculatedMember>. Beide Ansätze können im selben Cube koexistieren.
Siehe Fortgeschritten – Berechnete Members für die vollständige <CalculatedMember>-Referenz.