Zum Inhalt springen

YAML-Schemas

XML-Schemas sind präzise, aber sie bestehen aus vielen spitzen Klammern. YAML-Schemas geben Ihnen dasselbe Modell in einem Format, das auf einen Blick leichter zu lesen, in einem Pull Request leichter zu prüfen und mit #-Kommentaren leicht zu annotieren ist. Die Saiku-Engine behandelt sie identisch – der Konverter baut in beiden Fällen denselben getypten Objektgraphen.

Warum YAML?

  • Lesbar. Weniger Zeichen für dieselbe Information – eine typische Dimension, die 60 XML-Zeilen einnimmt, passt in 20 YAML-Zeilen.
  • Diffbar. Strukturelle Änderungen (Hinzufügen einer Ebene, Umbenennen einer Measure) erzeugen saubere, lesbare Diffs anstelle von Attribut-Suppen-Diffs.
  • Kommentierbar. Sie können jeden Abschnitt mit #-Kommentaren annotieren; XML-Kommentare sind legal, überleben aber selten Round-Trips durch grafische Tools.
  • Round-Trip-fähig. XML → YAML → XML erzeugt byte-äquivalente Abfrageergebnisse. Sie können Ihre bestehenden Schemas jederzeit mit der CLI konvertieren.

Top-Level-Struktur

Ein vollständiges M4-YAML-Schema verwendet diese Top-Level-Schlüssel (nur schema ist erforderlich):

schema: # required — schema header
name: "FoodMart"
metamodel_version: "4.0"
annotations: # optional — schema-level metadata
caption.de_DE: "Verkaufen"
physical_schema: # optional — tables, calculated columns, links
tables: [...]
links: [...]
shared_dimensions: # optional — named dimensions reused across cubes
Store: { ... }
Time: { ... }
cubes: # optional — one entry per cube
Sales: { ... }
roles: # optional — access-control roles
- name: "California manager"
schema_grant: { ... }

schema (Header)

Mappt auf <Schema name="..." metamodelVersion="...">.

SchlüsselErforderlichAnmerkungen
namejaAnzeigename des Schemas
metamodel_versionneinTypischerweise "4.0"

Kurzform (nur Name):

schema: FoodMart

Langform:

schema:
name: "FoodMart"
metamodel_version: "4.0"

annotations

Eine flache Map von name: text-Paaren. Unterstützt auf Schema-, Cube-, Dimensions-, Hierarchie-, Level-, Attribut-, Measure-, Calculated-Member- und Rollenebene. Mondrian verwendet punktqualifizierte Namen als Konvention für sprachspezifische Metadaten:

annotations:
caption.de_DE: "Verkaufen"
caption.fr_FR: "Ventes"
description.fr_FR: "Cube des ventes"

physical_schema

Deklariert die physischen Tabellen und die Fremdschlüssel-Beziehungen zwischen ihnen.

tables

Eine Liste von Tabellendefinitionen. Jeder Eintrag mappt auf ein <Table>-Element.

SchlüsselErforderlichAnmerkungen
namejaTabellenname in der Datenbank
aliasneinAlternativname, der an anderer Stelle im Schema verwendet wird (z. B. für Self-Joins)
schemaneinDatenbankschema-Qualifizierer (z. B. dbo)
key_columnneinEinzelspaltiger Primärschlüssel-Kurzschreibweise
keyneinMehrspaltiger Primärschlüssel – Liste von Spaltennamen-Strings
calculated_columnsneinAbgeleitete Spalten, definiert als SQL-Ausdrücke (siehe unten)

key_column und key schließen sich gegenseitig aus. Verwenden Sie key_column für eine einzelne Spalte; verwenden Sie key für zusammengesetzte Schlüssel. Faktentabellen haben typischerweise keinen deklarierten Schlüssel.

physical_schema:
tables:
- name: "customer"
key:
- "customer_id"
- name: "product"
key_column: "product_id"
- name: "salary"
alias: "salary2" # second alias for a self-join
- name: "sales_fact_1997" # fact table — no key declared

calculated_columns

Virtuelle Spalten, die aus SQL-Ausdrücken berechnet werden. Mappt auf <ColumnDefs><CalculatedColumnDef>.

SchlüsselErforderlichAnmerkungen
namejaSpaltenname, der an anderer Stelle im Schema verwendet wird
typeneinMondrian-Typ: String, Numeric, Integer
expressionjaMap von SQL-Dialektname → SQL-Körper

Inline-Spaltenreferenzen in SQL-Körpern verwenden {col:column_name}- oder {col:table.column_name}-Tokens, die der Konverter zurück auf <Column>-Elemente parst:

- name: "customer"
key:
- "customer_id"
calculated_columns:
- name: "full_name"
type: "String"
expression:
oracle: "{col:fname} || ' ' || {col:lname}"
mysql: "CONCAT({col:fname}, ' ', {col:lname})"
mssql: "{col:fname} + ' ' + {col:lname}"
generic: "{col:fullname}"

Unterstützte Dialekt-Schlüssel umfassen generic, mysql, oracle, postgres, mssql, access, derby, db2, luciddb.

Fremdschlüssel-Beziehungen zwischen Tabellen. Jeder Eintrag mappt auf ein <Link source="..." target="...">-Element.

SchlüsselErforderlichAnmerkungen
sourcejaKind-Tabelle (Many-Seite)
targetjaEltern-Tabelle (One-Seite)
foreign_key_columnnein*Einzelner FK-Spaltenname (Kurzform)
foreign_keynein*Liste von FK-Spaltennamen (zusammengesetzter FK)

*Eines von foreign_key_column oder foreign_key ist erforderlich.

links:
- source: "product_class"
target: "product"
foreign_key:
- "product_class_id"
- source: "store"
target: "employee"
foreign_key:
- "store_id"

shared_dimensions

Eine Map von dimension_name: dimension_body. Der Map-Schlüssel wird zum name-Attribut auf dem resultierenden <Dimension>-Element. Geteilte Dimensionen leben außerhalb eines Cubes und können von mehreren Cubes referenziert werden.

SchlüsselErforderlichAnmerkungen
tableneinStandardtabelle für Attribute in dieser Dimension
keyneinName des Schlüsselattributs (muss mit einem Attribut-name übereinstimmen)
typenein"TIME" für Zeitdimensionen; bei Standard weglassen
attributesneinListe von Attributdefinitionen
hierarchiesneinListe von Hierarchiedefinitionen
annotationsneinMap von Annotationsname → Text

attributes

SchlüsselErforderlichAnmerkungen
namejaAnzeigename des Attributs
tableneinÜberschreibt die Standardtabelle der Dimension
key_columnnein*Einzelspaltiger Schlüssel
keynein*Mehrspaltiger Schlüssel – Liste von "table.column"- oder "column"-Strings
name_columnneinSpalte, die für den Member-Anzeigenamen verwendet wird
name_columnsneinMehrspaltiger Name – Liste von Spalten-Strings
order_by_columnneinSpalte für die Member-Sortierung
caption_columnneinSpalte für die Member-Beschriftung
level_typeneinZeitgranularität: TimeYears, TimeQuarters, TimeMonths, TimeWeeks, TimeDays
datatypeneinBoolean, Numeric, Integer, String (Standard String weglassen)
has_hierarchyneinfalse unterdrückt die automatisch erzeugte Single-Attribut-Hierarchie (Standard true; bei true weglassen)
hierarchy_all_member_nameneinAll-Member-Label für die auto-generierte Hierarchie
hierarchy_all_member_captionneinAll-Member-Caption für die auto-generierte Hierarchie
hierarchy_default_memberneinMDX-eindeutiger Name des Default-Members für die auto-generierte Hierarchie
hierarchy_has_allneinfalse unterdrückt die All-Ebene in der auto-generierten Hierarchie
propertiesneinListe von Geschwister-Attributnamen, die Properties dieses Attributs sind
annotationsneinMap von Annotationsname → Text

*key_column und key schließen sich gegenseitig aus. Für Cross-Table-Referenzen innerhalb von key oder name_columns qualifizieren Sie mit table.column:

attributes:
- name: "Brand Name"
table: "product"
key:
- "product_class.product_family" # qualified cross-table ref
- "product_class.product_department"
- "brand_name" # unqualified — belongs to default table
name_column: "brand_name"
has_hierarchy: false

hierarchies

SchlüsselErforderlichAnmerkungen
namejaHierarchiename
all_member_nameneinLabel für den All-Member
default_memberneinMDX-eindeutiger Name des Default-Members
has_allneinfalse unterdrückt die All-Ebene
levelsjaGeordnete Liste von Ebenen
annotationsneinMap von Annotationsname → Text

Jeder Eintrag in levels ist entweder ein einfacher String (wenn der Ebenenname dem Attributnamen entspricht) oder eine Map {name, attribute} (wenn sie sich unterscheiden):

hierarchies:
- name: "Stores"
all_member_name: "All Stores"
levels:
- "Store Country" # bare string
- "Store State"
- "Store City"
- "Store Name"
- name: "Education Level"
levels:
- name: "Education Level" # map form — level name differs from attribute
attribute: "Education"

Vollständiges Beispiel – die geteilte Dimension Store aus FoodMart:

shared_dimensions:
Store:
table: "store"
key: "Store Id"
attributes:
- name: "Store Country"
key_column: "store_country"
has_hierarchy: false
- name: "Store State"
key_column: "store_state"
has_hierarchy: false
- name: "Store City"
key:
- "store_state"
- "store_city"
name_column: "store_city"
has_hierarchy: false
- name: "Store Id"
key_column: "store_id"
has_hierarchy: false
- name: "Store Name"
key_column: "store_name"
has_hierarchy: false
properties:
- "Store Type"
- "Store Manager"
- name: "Store Type"
key_column: "store_type"
hierarchy_all_member_name: "All Store Types"
hierarchies:
- name: "Stores"
all_member_name: "All Stores"
levels:
- "Store Country"
- "Store State"
- "Store City"
- "Store Name"

cubes

Eine Map von cube_name: cube_body. Der Map-Schlüssel wird zum name des Cubes.

SchlüsselErforderlichAnmerkungen
default_measureneinName der Standard-Measure
annotationsneinMap von Annotationsname → Text
dimensionsneinListe von Dimensionsnutzungen und lokalen Dimensionsdefinitionen
measure_groupsneinListe von Measure-Group-Definitionen
calculated_membersneinListe von berechneten Member-Definitionen
named_setsneinListe von benannten Set-Definitionen

dimensions (auf Cube-Ebene)

Jeder Eintrag ist entweder eine Nutzung (eine Referenz auf eine geteilte Dimension) oder eine lokale Definition (eine Inline-Dimension, die nur für diesen Cube definiert ist).

Nutzung – eine Map nur mit source:

dimensions:
- source: "Store"
- source: "Time"
- source: "Product"

Lokale Definition – eine Map mit name plus dem vollständigen Dimensionskörper (gleiche Schlüssel wie shared_dimensions):

dimensions:
- name: "Customer"
table: "customer"
key: "Name"
attributes:
- name: "Country"
key_column: "country"
has_hierarchy: false
- name: "Name"
key_column: "customer_id"
name_column: "full_name"
order_by_column: "full_name"
has_hierarchy: false
hierarchies:
- name: "Customers"
all_member_name: "All Customers"
levels:
- "Country"
- "State Province"
- "City"
- "Name"

measure_groups

SchlüsselErforderlichAnmerkungen
nameneinName der Measure-Group; optional für die primäre Gruppe
tablejaFaktentabellen- oder Aggregat-Tabellenname
typenein"aggregate" für aggregierte Measure-Groups; bei "fact" weglassen
approx_row_countneinHinweis für die ungefähre Faktentabellen-Zeilenanzahl (String, z. B. "86837")
ignore_unrelated_dimensionsneintrue behandelt nicht verwandte Dimensionen als [All], statt null zurückzugeben
measuresneinListe von Measure- oder Measure-Ref-Definitionen
dimension_linksneinListe von Links von dieser Measure-Group zu ihren Dimensionen

measures

Jeder Eintrag ist entweder eine Measure-Definition (hat name) oder eine Measure-Referenz (hat ref, verwendet in aggregierten Measure-Groups).

Measure-Definition:

SchlüsselErforderlichAnmerkungen
namejaAnzeigename der Measure
columnneinQuellspalte
aggregatorjasum, count, distinct-count, min, max, avg
format_stringneinMDX-Format-String (z. B. "#,###.00", "Standard", "Currency")
datatypeneinNumeric, Integer, String
propertiesneinListe von {name, value}- oder {name, expression}-Maps
annotationsneinMap von Annotationsname → Text

Measure-Referenz (nur in aggregierten Measure-Groups):

SchlüsselErforderlichAnmerkungen
refjaName der referenzierten Measure aus der primären Gruppe
agg_columnneinSpalte in der Aggregat-Tabelle mit dem vor-aggregierten Wert
measures:
- name: "Unit Sales"
column: "unit_sales"
aggregator: "sum"
format_string: "Standard"
- name: "Customer Count"
column: "customer_id"
aggregator: "distinct-count"
format_string: "#,###"
# Inside an aggregate measure group:
- ref: "Unit Sales"
agg_column: "unit_sales_sum"
- ref: "Fact Count"
agg_column: "fact_count"

Jeder Link hat ein type-Feld, das den Join-Typ bestimmt.

foreign_key – der Standard-Join von der Faktentabelle zur Dimension über eine FK-Spalte:

SchlüsselErforderlichAnmerkungen
typeja"foreign_key"
dimensionjaDimensionsname
foreign_key_columnnein*Einzelner FK-Spaltenname
foreign_keynein*Liste von FK-Spaltennamen (zusammengesetzter FK)
attributeneinAttribut, mit dem gejoint wird, wenn der FK nicht auf den Dimensionsschlüssel zeigt

copy – die Aggregat-Tabelle erbt Dimensionsdaten aus einer anderen Measure-Group:

SchlüsselErforderlichAnmerkungen
typeja"copy"
dimensionjaDimensionsname
column_refsneinListe von {table, name, agg_column}-Maps

no_link – diese Measure-Group hat keinen Link zu dieser Dimension:

SchlüsselErforderlichAnmerkungen
typeja"no_link"
dimensionjaDimensionsname

fact – die Daten der Dimension kommen direkt aus der Faktentabelle:

SchlüsselErforderlichAnmerkungen
typeja"fact"
dimensionjaDimensionsname

reference – die Dimension wird indirekt über das Attribut einer anderen Dimension erreicht:

SchlüsselErforderlichAnmerkungen
typeja"reference"
dimensionjaDimensionsname
via_dimensionneinName der Zwischendimension
via_attributeneinAttribut auf der Zwischendimension, das zum Join verwendet wird

Vollständiges Measure-Group-Beispiel:

measure_groups:
- name: "Sales"
table: "sales_fact_1997"
measures:
- name: "Unit Sales"
column: "unit_sales"
aggregator: "sum"
format_string: "Standard"
- name: "Store Cost"
column: "store_cost"
aggregator: "sum"
format_string: "#,###.00"
- name: "Customer Count"
column: "customer_id"
aggregator: "distinct-count"
format_string: "#,###"
dimension_links:
- type: "foreign_key"
dimension: "Store"
foreign_key_column: "store_id"
- type: "foreign_key"
dimension: "Time"
foreign_key_column: "time_id"
- type: "foreign_key"
dimension: "Product"
foreign_key_column: "product_id"
- table: "agg_c_special_sales_fact_1997"
type: "aggregate"
measures:
- ref: "Fact Count"
agg_column: "fact_count"
- ref: "Unit Sales"
agg_column: "unit_sales_sum"
dimension_links:
- type: "foreign_key"
dimension: "Store"
foreign_key_column: "store_id"
- type: "copy"
dimension: "Time"

calculated_members

SchlüsselErforderlichAnmerkungen
namejaMembername
dimensionneinZieldimension (z. B. "Measures")
hierarchyneinMDX-eindeutiger Name der Zielhierarchie
parentneinMDX-eindeutiger Name des Eltern-Members
formulaneinMDX-Formel
format_stringneinMDX-Format-String
captionneinAnzeige-Caption in Client-Tools
descriptionneinMenschenlesbare Beschreibung
visibleneinfalse versteckt den Member vor Client-Tools (Standard true; bei true weglassen)
cell_formatterneinBenutzerdefinierter Cell Formatter – {class_name} oder {script: {language, body}}
propertiesneinListe von {name, value}- oder {name, expression}-Maps
annotationsneinMap von Annotationsname → Text
calculated_members:
- name: "Profit"
dimension: "Measures"
formula: "[Measures].[Store Sales] - [Measures].[Store Cost]"
properties:
- name: "FORMAT_STRING"
value: "$#,##0.00"
- name: "Profit Growth"
dimension: "Measures"
formula: >-
([Measures].[Profit] - [Measures].[Profit last Period])
/ [Measures].[Profit last Period]
properties:
- name: "FORMAT_STRING"
value: "0.0%"

named_sets

named_sets:
- name: "Top Sellers"
formula: "TopCount([Warehouse].[Warehouse Name].MEMBERS, 5, [Measures].[Warehouse Sales])"

roles

Eine Liste von Rollendefinitionen.

SchlüsselErforderlichAnmerkungen
namejaRollenname
class_nameneinJava-Klasse, die benutzerdefinierte Rollenlogik implementiert
schema_grantneinTop-Level-Grant

schema_grant hat access ("all", "none", "all_dimensions", "custom") und eine optionale cubes-Liste. Jeder Cube-Grant kann dimensions- und hierarchies-Listen mit eigenen Grants enthalten. Hierarchie-Grants unterstützen top_level, bottom_level, rollup_policy ("full", "partial", "hidden") und eine members-Liste.

roles:
- name: "California manager"
schema_grant:
access: "none"
cubes:
- cube: "Sales"
access: "all"
dimensions:
- dimension: "Gender"
access: "none"
hierarchies:
- hierarchy: "[Store].[Stores]"
access: "custom"
top_level: "[Store].[Stores].[Store Country]"
members:
- member: "[Store].[Stores].[USA].[CA]"
access: "all"
- member: "[Store].[Stores].[USA].[CA].[Los Angeles]"
access: "none"

XML vs. YAML im Vergleich

Hier ist dieselbe Time-Dimension in beiden Formaten:

shared_dimensions:
Time:
table: "time_by_day"
key: "Time Id"
type: "TIME"
attributes:
- name: "Year"
key_column: "the_year"
level_type: "TimeYears"
has_hierarchy: false
- name: "Quarter"
key:
- "the_year"
- "quarter"
name_column: "quarter"
level_type: "TimeQuarters"
has_hierarchy: false
- name: "Month"
key:
- "the_year"
- "month_of_year"
name_column: "the_month"
level_type: "TimeMonths"
has_hierarchy: false
- name: "Time Id"
key_column: "time_id"
has_hierarchy: false
hierarchies:
- name: "Time"
has_all: false
levels:
- "Year"
- "Quarter"
- "Month"

Kodierung von Spaltenreferenzen

Zwei Kodierungskonventionen erscheinen im YAML-Format:

table.column-qualifizierte Referenzen – innerhalb von key- und name_columns-Listen wird eine Spalte, die zu einer nicht-Standard-Tabelle gehört, als "table_name.column_name" geschrieben. Der Konverter teilt am ersten ., um Tabelle und Spalte wiederherzustellen:

key:
- "product_class.product_family"
- "product_class.product_department"
- "brand_name" # unqualified — belongs to the default table

{col:...}-Tokens – innerhalb von SQL-Ausdruckskörpern für berechnete Spalten verwenden Inline-Spaltenreferenzen {col:column_name} oder {col:table.column_name}. Der Konverter parst diese zurück auf <Column>-Elemente:

expression:
mysql: "CONCAT({col:fname}, ' ', {col:lname})"
generic: "{col:fullname}"

Bekannte Einschränkungen

  1. Bezeichner, die einen Literal-Punkt enthalten – die table.column-Kodierung teilt am ersten .-Zeichen. Tabellen- oder Spaltennamen, die selbst ein . enthalten (z. B. quoting Identifier), durchlaufen den Round-Trip nicht korrekt. Dasselbe gilt für {col:table.column}-Tokens.

  2. Whitespace in SQL berechneter Spalten – der YAML-Serialisierer kann zusätzlichen führenden Whitespace in SQL-Körpern einführen, wenn eine Datei maschinell generiert und erneut gelesen wurde. SQL mit signifikantem führenden Whitespace kann beim zweiten Round-Trip zusätzliche Einrückung erhalten.

  3. $ref erfordert eine Datei-URL – die $ref-Include-Auflösung funktioniert nur, wenn das Schema über eine Catalog=file:///...-Connect-String-Property geladen wird. Über CatalogContent geladene Schemas haben kein Basisverzeichnis, und die $ref-Auflösung wird übersprungen.


Verwandt

  • CLI – Konvertierung zwischen XML und YAML und Linting von Schemas über die Kommandozeile.
  • Schema-Designer – Generieren eines XML-Schemas aus einer natürlichsprachlichen Beschreibung.
  • Schemas – Verwalten und Bearbeiten gespeicherter Schemas im Saiku-Dashboard.