Inteligencia temporal (YoY, PoP, YTD, móviles declarativos)
Mondrian-4 admite un elemento de schema <TimeCalc> que declara métricas comunes de inteligencia temporal. El loader del schema desazucara cada declaración en un <CalculatedMember> validado sobre [Measures] — así que usted indica lo que quiere en lugar de escribir y mantener fórmulas MDX a mano.
¿Por qué inteligencia temporal declarativa?
Sin <TimeCalc>, el crecimiento year-over-year requiere un miembro calculado como:
<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>Con <TimeCalc> la misma métrica es:
<TimeCalc name="Revenue YoY" type="yoy" measure="Revenue" timeDimension="Calendar" formatString="0.0%"/>El loader genera el MDX por usted, valida que la medida y la dimensión temporal referenciadas existen, y lanza un error en tiempo de carga en lugar de producir un resultado silenciosamente incorrecto.
Requisito previo: una dimensión Time tipada
<TimeCalc> requiere que el cubo tenga una dimensión Time tipada — un <Dimension> con type="TIME" cuya jerarquía tiene niveles nombrados para year, quarter y month. El nivel year debe llevar levelType="TimeYears", y los niveles quarter y month deben llevar levelType="TimeQuarters" y levelType="TimeMonths" respectivamente. Los cálculos dentro del año (ytd, pop, rolling) requieren al menos un nivel month en la jerarquía.
Una dimensión Calendar mínima que satisface el requisito:
<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"Colocación en el schema
Los elementos <TimeCalc> se envuelven en un bloque <TimeCalcs> dentro de un <Cube>, al mismo nivel que <CalculatedMembers>:
<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"Referencia de atributos
| Atributo | clave XML / YAML | Requerido | Descripción |
|---|---|---|---|
name | name | sí | El nombre del miembro calculado generado. Aparece en [Measures] como cualquier otra medida. |
type | type | sí | El tipo de métrica: yoy, pop, ytd o rolling. Consulte Tipos de métrica abajo. |
measure | measure | sí | El nombre de un <Measure> existente en el cubo. El loader rechaza una medida desconocida en la carga del schema. |
timeDimension | time_dimension | condicional | El nombre de una dimensión type="TIME". Puede omitirse cuando el cubo tiene exactamente una dimensión TIME; requerido cuando tiene más de una. |
window | window | solo rolling | Número entero de períodos a incluir en la ventana móvil. |
function | function | solo rolling | Función de agregación sobre la ventana: sum (por defecto) o avg. |
formatString | format_string | no | Cadena de formato MDX aplicada al miembro generado, por ejemplo "0.0%" o "#,###". |
Tipos de métrica
yoy — crecimiento year-over-year
Informa el cambio porcentual comparado con el mismo período del año anterior.
Forma de la fórmula:
([Measures].[<measure>] - (ParallelPeriod([<dim>].[<YearLevel>], 1, [<dim>].CurrentMember), [Measures].[<measure>]))/(ParallelPeriod([<dim>].[<YearLevel>], 1, [<dim>].CurrentMember), [Measures].[<measure>])La llamada ParallelPeriod navega a la misma posición relativa un año atrás usando el nivel TimeYears. El resultado es NULL para el primer año completo de datos (sin año anterior disponible).
pop — crecimiento period-over-period
Informa el cambio porcentual comparado con el período inmediatamente anterior (el período justo antes del actual en el mismo nivel).
Forma de la fórmula:
([Measures].[<measure>] - (PrevMember([<dim>].CurrentMember), [Measures].[<measure>]))/(PrevMember([<dim>].CurrentMember), [Measures].[<measure>])PrevMember retrocede una posición en el orden natural de la dimensión. Los resultados son NULL para el primer miembro de un nivel (sin predecesor).
ytd — acumulado year-to-date
Informa el valor acumulado de la medida desde el inicio del año actual hasta el período actual.
Forma de la fórmula:
Aggregate(Ytd([<dim>].CurrentMember), [Measures].[<measure>])Ytd() devuelve el conjunto de todos los períodos desde el primer período del año actual hasta el período actual. Aggregate aplica la agregación nativa de la medida (típicamente sum) sobre ese conjunto.
rolling — ventana móvil
Informa el agregado de la medida sobre los últimos window períodos, usando sum o avg.
Forma de la fórmula (avg, window=3):
Avg(LastPeriods(3, [<dim>].CurrentMember), [Measures].[<measure>])Forma de la fórmula (sum, window=N):
Sum(LastPeriods(N, [<dim>].CurrentMember), [Measures].[<measure>])LastPeriods(N, member) devuelve el conjunto de los N períodos que terminan en el miembro actual. Si hay menos de N períodos disponibles (por ejemplo, al inicio del historial de datos), la ventana se reduce a tantos períodos como existan — no rellena con ceros.
Comportamiento de validación
El loader es fail-closed: la carga del schema se aborta con un mensaje de error claro si se detecta alguna de las siguientes condiciones.
| Condición | Error |
|---|---|
measure nombra un miembro que no existe en el cubo | TimeCalc "X": measure "Y" not found in cube |
No se especifica timeDimension y el cubo tiene cero dimensiones TIME | TimeCalc "X": cube has no TIME dimension |
No se especifica timeDimension y el cubo tiene más de una dimensión TIME | TimeCalc "X": cube has multiple TIME dimensions — specify timeDimension |
timeDimension nombra una dimensión que no existe en el cubo | TimeCalc "X": timeDimension "Y" not found |
type="rolling" y window falta o no es un entero positivo | TimeCalc "X": rolling type requires a positive integer window |
type="yoy" pero no existe nivel TimeYears en la dimensión | TimeCalc "X": TIME dimension has no TimeYears level |
No hay resultado silenciosamente incorrecto — cada mala configuración se captura antes de que se ejecute la primera consulta.
Ejemplo trabajado: ingresos mensuales de la demo Bank
La demo Bank incluye un cubo Monthly Revenue sobre una serie mensual de ingresos. Los datos crudos para dos años:
| Año | Mes | Revenue |
|---|---|---|
| 2024 | Ene | 100 |
| 2024 | Feb | 200 |
| 2024 | Mar | 300 |
| 2025 | Ene | 150 |
| 2025 | Feb | 250 |
| 2025 | Mar | 350 |
El cubo declara los cuatro tipos de <TimeCalc> contra la dimensión Calendar (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"Resultados de referencia
| Celda | Valor | Cómo |
|---|---|---|
| Revenue YoY en [Calendar].[2025].[Q1].[Jan 2025] | 0.5 (50%) | (150 − 100) / 100 = 0.5 |
| Revenue PoP en [Calendar].[2024].[Q1].[Feb 2024] | 1.0 (100%) | (200 − 100) / 100 = 1.0 |
| Revenue YTD en [Calendar].[2024].[Q1].[Mar 2024] | 600 | 100 + 200 + 300 = 600 |
| Revenue R3 en [Calendar].[2025].[Q1].[Mar 2025] | 250 | avg(150, 250, 350) = 250 |
Consulta MDX de ejemplo
SELECT { [Measures].[Revenue], [Measures].[Revenue YoY], [Measures].[Revenue PoP], [Measures].[Revenue YTD], [Measures].[Revenue R3] } ON COLUMNS, [Calendar].[Month].Members ON ROWSFROM [Monthly Revenue]Resultado parcial (2024–2025 enero hasta marzo):
| Mes | Revenue | YoY | PoP | YTD | R3 |
|---|---|---|---|---|---|
| Ene 2024 | 100 | — | — | 100 | 100 |
| Feb 2024 | 200 | — | 100.0% | 300 | 150 |
| Mar 2024 | 300 | — | 50.0% | 600 | 200 |
| Ene 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 |
Los guiones (—) indican NULL — no hay datos del año anterior o no hay período predecesor disponible.
Relación con <CalculatedMembers>
Las declaraciones <TimeCalc> se desazucan en tiempo de carga en elementos <CalculatedMember> sobre [Measures]. Los miembros generados son indistinguibles de los miembros calculados escritos a mano en tiempo de consulta: aparecen en enumeraciones de miembros XMLA, responden a FORMAT_STRING, y pueden ser referenciados por otros miembros calculados.
Si necesita una fórmula que <TimeCalc> no pueda expresar — por ejemplo, una métrica mezclada personalizada o una razón multi-medida antes de una comparación temporal — use directamente un <CalculatedMember> plano. Los dos enfoques pueden coexistir en el mismo cubo.
Consulte Avanzado — Miembros calculados para la referencia completa de <CalculatedMember>.