Skip to content

Cubes and measures

The Cube element

A cube (<Cube>) is a named collection of dimensions and measures.

Dimensions live inside a <Dimensions> holder element. By convention they are declared first, followed by measures organised into measure groups under a <MeasureGroups> holder element. A measure group is a collection of measures that share the same fact table. Simple cubes have exactly one measure group; more advanced cubes can have several — for example when you want to combine a transaction fact table with a pre-aggregated rollup table.

Cube attributes

AttributeRequiredDefaultDescription
nameyesDisplay name used in MDX queries
defaultMeasurenoMeasure selected when none is specified in the query
captionnoOverride display name for client tools
descriptionnoHuman-readable description
visiblenotrueWhether the cube appears to client tools
cachenotrueWhether Mondrian caches aggregates for this cube
enablednotrueDisabling a cube hides it without removing it
enableScenariosnofalseEnables write-back / what-if scenarios

How fact tables and dimensions connect

The [Sales] cube in the schema structure example has its measure group based on the table "sales_fact_1997". Every table referenced in the logical schema must also appear in the <PhysicalSchema> block.

The fact table contains the columns from which measures are calculated, plus foreign key columns that link to dimension tables. Mondrian needs to know about all of them:

  • Each measure column appears inside a <Measure> definition.
  • Each foreign key column appears inside a <ForeignKeyLink> element, connecting the measure group to the appropriate dimension.
measure_groups:
- name: "Sales"
table: "sales_fact_1997"
measures:
- name: "Unit Sales"
column: "unit_sales"
aggregator: "sum"
format_string: "#,###"
- name: "Store Sales"
column: "store_sales"
aggregator: "sum"
format_string: "#,###.##"
- name: "Store Cost"
column: "store_cost"
aggregator: "sum"
format_string: "#,###.00"
dimension_links:
- type: "foreign_key"
dimension: "Customer"
foreign_key_column: "customer_id"
- type: "foreign_key"
dimension: "Time"
foreign_key_column: "time_id"

Measures

Measure attributes

Each <Measure> element has the following attributes:

AttributeRequiredDefaultDescription
nameyesDisplay name used in MDX queries
columnyes*Column in the fact table. Use a calculated column name if the value is derived
aggregatoryesAggregation function — see below
formatStringnoHow the value is formatted for display
datatypenoNumericHow values are stored in Mondrian’s cache and returned via XML for Analysis
captionnoOverride display name for client tools
descriptionnoHuman-readable description
visiblenotrueWhether the measure appears to client tools
formatternoFully-qualified class name of a custom cell formatter
tablenoOverride the measure group’s table for this measure (advanced)

* column is required unless you point to a calculated column defined in the physical schema.

Aggregator types

The aggregator attribute supports the following values:

ValueMeaning
sumSum of all values
countRow count
minMinimum value
maxMaximum value
avgArithmetic mean
distinct-countCount of distinct values
medianMedian (50th percentile) — non-additive
percentileA percentile (set percentile="0..100", default 50) — non-additive

distinct-count has limitations when the cube contains a parent-child hierarchy.

Non-additive aggregators: median and percentile

median and percentile are non-additive leaf aggregators: there is no median-of-medians, so they cannot be combined from sub-aggregates. Saiku pushes them down to SQL as PERCENTILE_CONT(fraction) WITHIN GROUP (ORDER BY column) and computes them at the exact grain you queried — they are deliberately excluded from aggregate-table substitution and segment rollup-from-cache (so you never get a wrong “median of medians”).

measures:
- name: "Median Order Value"
column: "order_total"
aggregator: "median"
- name: "P90 Latency"
column: "latency_ms"
aggregator: "percentile"
percentile: "90"

They require the Calcite backend (the Saiku default) on a database that supports PERCENTILE_CONT — PostgreSQL, Oracle, SQL Server, Snowflake, BigQuery, H2, DuckDB, and similar. On a backend without it the query is refused with a clear error rather than returning a wrong number. The trade-off is intentional: you lose pre-aggregation/cache-rollup for these measures, in exchange for a correct value at the queried grain.

Data types

The datatype attribute controls how cell values are stored in Mondrian’s cache and returned over XML for Analysis. Accepted values are String, Integer, Numeric, Boolean, Date, Time, and Timestamp. The default is Numeric, except for count and distinct-count measures, which default to Integer.

Format strings

The optional formatString attribute controls how a value is printed. The , and . symbols are locale-sensitive — if you are running in Italian, #,###.00 might produce 48.123,45. A few common patterns:

PatternExample output
#,###32,910
#,###.##69,798.23
#,###.0069,798.23
$#,##0.00$69,798.23
Standardlocale default

For advanced date patterns and conditional formats see the MDX format strings reference.

Caption

A measure can have a caption attribute that is returned instead of its name by client APIs. This is useful when you want to localise a measure name or display special characters:

measures:
- name: "Sum X"
column: "sum_x"
aggregator: "sum"
caption: "Σ X"

Calculated columns in measures

Rather than pointing a measure at a raw column, you can derive the value from a SQL expression. Define a calculated column in the <PhysicalSchema> declaration of the fact table and then reference it by name in the <Measure>:

calculated_columns:
- name: "promotion_sales"
expression:
generic: "(case when {col:promotion_id} = 0 then 0 else {col:store_sales}\
\ end)"

Then reference that column just like any other:

measures:
- name: "Promotion Sales"
column: "promotion_sales"
aggregator: "sum"
format_string: "#,###.00"

The <PhysicalSchema> gathers all implementation details in one place. The <Measure> definition does not need to know — or care — that promotion_sales is calculated. Every time Mondrian needs to access it, the engine substitutes the SQL expression instead. Arbitrary SQL expressions are supported, including subqueries, as long as the underlying database can evaluate them in an aggregate context.

When a dimension’s key spans more than one column, you express that with a multi-column <Key> block on the <Attribute>:

attributes:
- name: "Quarter"
key:
- "the_year"
- "quarter"

If there is only one key column, <Key> and the keyColumn shorthand attribute are equivalent — use whichever is clearer. You do not need to specify nameColumn separately when it defaults to the last column in the composite key.

When a dimension table has a composite primary key, the <ForeignKeyLink> in the fact table’s <MeasureGroup> must supply one foreign-key column per column in the composite key. See Physical schema for the full link reference.