A schema is an XML file in schema/ that describes one semantic model: SQL sources, cubes, dimensions, hierarchies, measures, calculated members and roles. This page covers the elements used in the eMondrian samples (schema/OnTime.xml, schema/Foodmart.xml).

eMondrian accepts both the attribute-based style shown here (DimensionAttribute, AttributeMapping, sourceAttribute) and the classic Mondrian style (Level column="…" inside a Hierarchy with its own Table). For the classic elements see the original Mondrian schema documentation.

Validate your schema

The MCP tool validate_schema reports problems the engine ignores silently — misspelled attributes, key mismatches. See AI agents (MCP).

Skeleton

<?xml version="1.0" encoding="UTF-8"?>
<Schema name="MySales">
 
  <!-- Shared SQL views and dimensions -->
  <View alias="dim_region"> … </View>
 
  <Cube name="Sales" cache="true" enabled="true">
    <View alias="fact"> … </View>          <!-- or <Table name="…"/> -->
    <Dimension …> … </Dimension>
    <Measure …/>
    <CalculatedMember …> … </CalculatedMember>
  </Cube>
 
  <Role name="…"> … </Role>
</Schema>

Sources: Table and View

A cube’s fact source and a dimension’s source are either a table or a SQL view.

<Table name="sales_fact_1997"/>
<View alias="dim_airline">
  <SQL dialect="clickhouse"><![CDATA[
    SELECT Reporting_Airline FROM ontime GROUP BY Reporting_Airline
  ]]></SQL>
  <SQL dialect="generic"><![CDATA[
    SELECT Reporting_Airline FROM ontime GROUP BY Reporting_Airline
  ]]></SQL>
</View>
Attribute / childMeaning
View aliasName you refer to from dimensions (table="dim_airline")
SQL dialectWhich database the SQL is for: clickhouse, postgres, generic, …

eMondrian picks the <SQL> variant matching the running database and falls back to generic. If neither exists, the schema fails to load. Always add a generic variant unless the SQL is truly database-specific.

<View> elements at schema level can be shared by several cubes; a <View> inside <Cube> is that cube’s fact source.

Cube

<Cube name="OnTime (attributes)" cache="true" enabled="true">
AttributeMeaning
nameCube name shown to clients
cacheCache results for this cube (true recommended)
enabledfalse hides the cube without deleting it
defaultMeasureMeasure used when a query names none

Children, in order: fact source (Table or View), dimensions (Dimension, DimensionUsage), Measures, CalculatedMembers, NamedSets.

Dimension

<Dimension name="Airline" foreignKey="Reporting_Airline"
           table="dim_airline" primaryKey="Reporting_Airline">
  <AttributeMapping attribute="Airline" factColumn="Reporting_Airline"/>
  <DimensionAttribute name="Airline" usage="Key">
    <KeyColumn columnName="Reporting_Airline" dataType="String"/>
  </DimensionAttribute>
  <Hierarchy hasAll="true" allMemberName="All Airlines">
    <Level name="Airline" uniqueMembers="true" sourceAttribute="Airline"/>
  </Hierarchy>
</Dimension>
AttributeMeaning
nameDimension name
tableSource table or view alias for the dimension
primaryKeyKey column in the dimension source
foreignKeyColumn in the fact source that joins to primaryKey

Element order is significant

Inside <Dimension>: AttributeMapping → DimensionAttribute → Hierarchy. Children are parsed by position.

Shared dimensions: DimensionUsage

Define a dimension once at schema level and use it in several cubes:

<DimensionUsage name="Store" source="Store" foreignKey="store_id"/>

DimensionAttribute

One usable column of a dimension.

<DimensionAttribute name="Month">
  <KeyColumn columnName="month_of_year" dataType="Numeric"/>
  <NameColumn columnName="the_month" dataType="String"/>
</DimensionAttribute>
Attribute / childMeaning
nameAttribute name, referenced by Level sourceAttribute and Property sourceAttribute
usage="Key"Marks the dimension’s key attribute
attributeHierarchyVisible="false"Attribute exists but isn’t shown as its own hierarchy
attributeHierarchyEnabled="false"No hierarchy is built for this attribute (use it as a member property)
levelTypeTime levels: TimeYears, TimeQuarters, TimeMonths, …
<KeyColumn>Column that identifies members. Use dataType (String, Numeric, Integer, Boolean) — not type, which is silently ignored
<NameColumn>Column for display names (e.g. month name instead of number)
<OrderByColumn>Column for sort order

AttributeMapping

<AttributeMapping attribute="Year" factColumn="Year"/>

Tells eMondrian that the attribute’s value is also available directly in the fact source, so it can filter and group without joining the dimension.

Only for unique keys

Use AttributeMapping only when the key is unique in the dimension source. If a key appears twice, the join fans out, and skipping it changes totals. The comment on Origin Airport in schema/OnTime.xml shows a real case: 2022 Flights come out as 6,767,533 or 6,729,125 depending on the path.

Hierarchy and Level

<Hierarchy hasAll="true" allMemberName="All Time">
  <Level name="Year"    uniqueMembers="true"  sourceAttribute="Year"/>
  <Level name="Quarter" uniqueMembers="false" sourceAttribute="Quarter" nameColumn="QuarterName"/>
  <Level name="Month"   uniqueMembers="false" sourceAttribute="Month"   nameColumn="MonthName"/>
</Hierarchy>
AttributeMeaning
Hierarchy hasAllAdd a top All member
Hierarchy allMemberNameName of the All member
Hierarchy nameNeeded only when a dimension has several hierarchies
Level nameLevel name
Level sourceAttributeThe DimensionAttribute this level uses
Level uniqueMemberstrue if a value is unique across the whole level (Year), false if only within its parent (Quarter)
Level nameColumnDisplay-name column
Level levelTypeTime level type

Member properties

<Level name="Store Name" uniqueMembers="true" sourceAttribute="Store Name">
  <Property name="Store Type" sourceAttribute="Store Type"/>
</Level>

Measure

<Measure name="Flights" column="Flights" aggregator="sum" formatString="#,###"/>
<Measure name="Total Arr Delay" column="ArrDelayMinutes" aggregator="sum" visible="false"/>
<Measure name="Distinct Tail Numbers" column="Tail_Number" aggregator="distinct-count" formatString="#,###"/>
AttributeMeaning
nameMeasure name
columnFact column
aggregatorsum, count, distinct-count, min, max, avg
formatStringDisplay format: #,###, #,##0.00, 0.00%, …
visible="false"Hidden helper measure, used in calculations

CalculatedMember

<CalculatedMember name="On-Time Rate" dimension="Measures" formatString="0.00%">
  <Formula>1 - ([Measures].[Late Arrivals] + [Measures].[Cancelled] + [Measures].[Diverted]) / [Measures].[Flights]</Formula>
</CalculatedMember>
Attribute / childMeaning
nameName
dimension="Measures"A calculated measure (calculated members on other dimensions are possible too)
formatStringDisplay format
<Formula>MDX expression

MDX reference: original Mondrian MDX documentation.

Role

<Role name="SampleRole">
  <SchemaGrant access="none">
    <CubeGrant cube="Sales" access="all">
      <DimensionGrant dimension="[Measures]" access="all"/>
      <HierarchyGrant hierarchy="[Time].[Time]" access="custom" rollupPolicy="partial">
        <MemberGrant member="[Time].[Time].[1997].[Q4]" access="all"/>
      </HierarchyGrant>
      <HierarchyGrant hierarchy="[Gender]" access="none"/>
    </CubeGrant>
  </SchemaGrant>
</Role>

Grants go from broad to narrow: schema → cube → dimension / hierarchy → member. access is all, none or custom.

Other elements

Used in Foodmart.xml, documented in the original Mondrian schema documentation: VirtualCube, NamedSet, Annotations, aggregate tables (AggName, AggExclude, …), drill-through actions, parent-child hierarchies (ParentColumn, Closure).

Applying changes

Restart after editing a schema file:

docker compose restart eMondrian

Errors appear in mondrian.log — see Logs.

See also