Schémas YAML
Les schémas XML sont précis mais ils représentent beaucoup de chevrons. Les schémas YAML vous donnent le même modèle dans un format plus facile à lire d’un coup d’œil, plus facile à examiner dans une pull request et facile à annoter avec des commentaires #. Le moteur Saiku les traite de manière identique — le convertisseur construit le même graphe typé d’objets dans les deux cas.
Pourquoi YAML ?
- Lisible. Moins de caractères pour la même information — une dimension typique qui prend 60 lignes XML tient en 20 lignes YAML.
- Diffable. Les changements structurels (ajouter un niveau, renommer une mesure) produisent des diffs propres et lisibles au lieu de diffs de soupe d’attributs.
- Commentable. Vous pouvez annoter n’importe quelle section avec des commentaires
#; les commentaires XML sont légaux mais survivent rarement aux round-trips à travers les outils graphiques. - Round-trip-able.
XML → YAML → XMLproduit des résultats de requête équivalents au byte près. Vous pouvez convertir vos schémas existants à tout moment avec la CLI.
Structure de haut niveau
Un schéma YAML M4 complet utilise ces clés de haut niveau (seul schema est requis) :
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 (en-tête)
Mappe à <Schema name="..." metamodelVersion="...">.
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom d’affichage du schéma |
metamodel_version | non | Typiquement "4.0" |
Forme courte (nom uniquement) :
schema: FoodMartForme longue :
schema: name: "FoodMart" metamodel_version: "4.0"annotations
Une map plate de paires name: text. Supportée aux niveaux schéma, cube, dimension, hiérarchie, niveau, attribut, mesure, membre calculé et rôle. Mondrian utilise des noms qualifiés par des points comme convention pour les métadonnées spécifiques à la locale :
annotations: caption.de_DE: "Verkaufen" caption.fr_FR: "Ventes" description.fr_FR: "Cube des ventes"physical_schema
Déclare les tables physiques et les relations de clé étrangère entre elles.
tables
Une liste de définitions de tables. Chaque entrée mappe à un élément <Table>.
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom de la table dans la base de données |
alias | non | Nom alternatif utilisé ailleurs dans le schéma (par ex. pour les self-joins) |
schema | non | Qualificateur de schéma de base de données (par ex. dbo) |
key_column | non | Raccourci de clé primaire à une seule colonne |
key | non | Clé primaire multi-colonnes — liste de chaînes de noms de colonnes |
calculated_columns | non | Colonnes dérivées définies comme expressions SQL (voir ci-dessous) |
key_column et key sont mutuellement exclusives. Utilisez key_column pour une seule colonne ; utilisez key pour les clés composites. Les tables de faits n’ont typiquement pas de clé déclarée.
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 declaredcalculated_columns
Colonnes virtuelles calculées à partir d’expressions SQL. Mappe à <ColumnDefs><CalculatedColumnDef>.
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom de colonne utilisé ailleurs dans le schéma |
type | non | Type Mondrian : String, Numeric, Integer |
expression | oui | Map de nom de dialecte SQL → corps SQL |
Les références de colonnes inline dans les corps SQL utilisent des tokens {col:column_name} ou {col:table.column_name}, que le convertisseur reparse en éléments <Column> :
- 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}"Les clés de dialecte supportées incluent generic, mysql, oracle, postgres, mssql, access, derby, db2, luciddb.
links
Relations de clé étrangère entre tables. Chaque entrée mappe à un élément <Link source="..." target="...">.
| Clé | Requise | Notes |
|---|---|---|
source | oui | Table enfant (côté many) |
target | oui | Table parente (côté one) |
foreign_key_column | non* | Nom de colonne FK unique (raccourci) |
foreign_key | non* | Liste de noms de colonnes FK (FK composée) |
*Une parmi foreign_key_column ou foreign_key est requise.
links: - source: "product_class" target: "product" foreign_key: - "product_class_id" - source: "store" target: "employee" foreign_key: - "store_id"shared_dimensions
Une map de dimension_name: dimension_body. La clé de la map devient l’attribut name sur l’élément <Dimension> résultant. Les dimensions partagées vivent en dehors de tout cube et peuvent être référencées par plusieurs cubes.
| Clé | Requise | Notes |
|---|---|---|
table | non | Table par défaut pour les attributs dans cette dimension |
key | non | Nom de l’attribut clé (doit correspondre à un name d’attribut) |
type | non | "TIME" pour les dimensions temporelles ; omettre pour standard |
attributes | non | Liste de définitions d’attributs |
hierarchies | non | Liste de définitions de hiérarchies |
annotations | non | Map de nom d’annotation → texte |
attributes
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom d’affichage de l’attribut |
table | non | Surcharge la table par défaut de la dimension |
key_column | non* | Clé à une seule colonne |
key | non* | Clé multi-colonnes — liste de chaînes "table.column" ou "column" |
name_column | non | Colonne utilisée pour le nom d’affichage du membre |
name_columns | non | Nom multi-colonnes — liste de chaînes de colonnes |
order_by_column | non | Colonne utilisée pour l’ordonnancement des membres |
caption_column | non | Colonne utilisée pour le caption du membre |
level_type | non | Granularité temporelle : TimeYears, TimeQuarters, TimeMonths, TimeWeeks, TimeDays |
datatype | non | Boolean, Numeric, Integer, String (le défaut String est omis) |
has_hierarchy | non | false supprime la hiérarchie auto-générée à un seul attribut (défaut true ; omettre quand vrai) |
hierarchy_all_member_name | non | Label du membre All pour la hiérarchie auto-générée |
hierarchy_all_member_caption | non | Caption du membre All pour la hiérarchie auto-générée |
hierarchy_default_member | non | Nom unique MDX du membre par défaut pour la hiérarchie auto-générée |
hierarchy_has_all | non | false supprime le niveau All dans la hiérarchie auto-générée |
properties | non | Liste de noms d’attributs frères qui sont des propriétés de cet attribut |
annotations | non | Map de nom d’annotation → texte |
*key_column et key sont mutuellement exclusives. Pour les références cross-table à l’intérieur de key ou name_columns, qualifiez avec 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: falsehierarchies
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom de la hiérarchie |
all_member_name | non | Label pour le membre All |
default_member | non | Nom unique MDX du membre par défaut |
has_all | non | false supprime le niveau All |
levels | oui | Liste ordonnée de niveaux |
annotations | non | Map de nom d’annotation → texte |
Chaque entrée dans levels est soit une chaîne simple (quand le nom du niveau est égal au nom de l’attribut) soit une map {name, attribute} (quand ils diffèrent) :
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"Exemple complet — la dimension partagée Store de 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
Une map de cube_name: cube_body. La clé de la map devient le name du cube.
| Clé | Requise | Notes |
|---|---|---|
default_measure | non | Nom de la mesure par défaut |
annotations | non | Map de nom d’annotation → texte |
dimensions | non | Liste d’utilisations de dimensions et définitions locales de dimensions |
measure_groups | non | Liste de définitions de measure groups |
calculated_members | non | Liste de définitions de membres calculés |
named_sets | non | Liste de définitions de named sets |
dimensions (au niveau du cube)
Chaque entrée est soit une utilisation (une référence à une dimension partagée) soit une définition locale (une dimension inline définie uniquement pour ce cube).
Utilisation — une map avec uniquement source :
dimensions: - source: "Store" - source: "Time" - source: "Product"Définition locale — une map avec name plus le corps complet de la dimension (mêmes clés que 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
| Clé | Requise | Notes |
|---|---|---|
name | non | Nom du measure group ; optionnel pour le groupe principal |
table | oui | Nom de la table de faits ou agrégée |
type | non | "aggregate" pour les measure groups agrégés ; omettre pour "fact" |
approx_row_count | non | Indice pour le nombre approximatif de lignes de la table de faits (chaîne, par ex. "86837") |
ignore_unrelated_dimensions | non | true traite les dimensions non liées comme [All] au lieu de retourner null |
measures | non | Liste de définitions de mesures ou de références de mesures |
dimension_links | non | Liste de liens de ce measure group vers ses dimensions |
measures
Chaque entrée est soit une définition de mesure (a name) soit une référence de mesure (a ref, utilisée dans les measure groups agrégés).
Définition de mesure :
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom d’affichage de la mesure |
column | non | Colonne source |
aggregator | oui | sum, count, distinct-count, min, max, avg |
format_string | non | Chaîne de format MDX (par ex. "#,###.00", "Standard", "Currency") |
datatype | non | Numeric, Integer, String |
properties | non | Liste de maps {name, value} ou {name, expression} |
annotations | non | Map de nom d’annotation → texte |
Référence de mesure (measure groups agrégés uniquement) :
| Clé | Requise | Notes |
|---|---|---|
ref | oui | Nom de la mesure référencée du groupe principal |
agg_column | non | Colonne dans la table agrégée contenant la valeur pré-agrégée |
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"dimension_links
Chaque lien a un champ type qui détermine le type de join.
foreign_key — le join standard de la table de faits vers la dimension via une colonne FK :
| Clé | Requise | Notes |
|---|---|---|
type | oui | "foreign_key" |
dimension | oui | Nom de la dimension |
foreign_key_column | non* | Nom de colonne FK unique |
foreign_key | non* | Liste de noms de colonnes FK (FK composée) |
attribute | non | Attribut à joindre quand la FK ne pointe pas vers la clé de dimension |
copy — la table agrégée hérite des données de dimension d’un autre measure group :
| Clé | Requise | Notes |
|---|---|---|
type | oui | "copy" |
dimension | oui | Nom de la dimension |
column_refs | non | Liste de maps {table, name, agg_column} |
no_link — ce measure group n’a pas de lien vers cette dimension :
| Clé | Requise | Notes |
|---|---|---|
type | oui | "no_link" |
dimension | oui | Nom de la dimension |
fact — les données de la dimension viennent directement de la table de faits :
| Clé | Requise | Notes |
|---|---|---|
type | oui | "fact" |
dimension | oui | Nom de la dimension |
reference — la dimension est atteinte indirectement via l’attribut d’une autre dimension :
| Clé | Requise | Notes |
|---|---|---|
type | oui | "reference" |
dimension | oui | Nom de la dimension |
via_dimension | non | Nom de la dimension intermédiaire |
via_attribute | non | Attribut sur la dimension intermédiaire utilisé pour joindre |
Exemple complet de measure group :
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
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom du membre |
dimension | non | Dimension cible (par ex. "Measures") |
hierarchy | non | Nom unique MDX de la hiérarchie cible |
parent | non | Nom unique MDX du membre parent |
formula | non | Formule MDX |
format_string | non | Chaîne de format MDX |
caption | non | Caption d’affichage montré dans les outils clients |
description | non | Description lisible par l’humain |
visible | non | false masque le membre des outils clients (défaut true ; omettre quand vrai) |
cell_formatter | non | Cell formatter personnalisé — {class_name} ou {script: {language, body}} |
properties | non | Liste de maps {name, value} ou {name, expression} |
annotations | non | Map de nom d’annotation → texte |
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
Une liste de définitions de rôles.
| Clé | Requise | Notes |
|---|---|---|
name | oui | Nom du rôle |
class_name | non | Classe Java implémentant la logique de rôle personnalisée |
schema_grant | non | Grant de haut niveau |
schema_grant a access ("all", "none", "all_dimensions", "custom") et une liste cubes optionnelle. Chaque grant de cube peut contenir des listes dimensions et hierarchies avec leurs propres grants. Les grants de hiérarchie supportent top_level, bottom_level, rollup_policy ("full", "partial", "hidden") et une liste members.
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 côte à côte
Voici la même dimension Time dans les deux formats :
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"<Dimension name='Time' table='time_by_day' key='Time Id' type='TIME'> <Attributes> <Attribute name='Year' keyColumn='the_year' levelType='TimeYears' hasHierarchy='false'/> <Attribute name='Quarter' levelType='TimeQuarters' nameColumn='quarter' hasHierarchy='false'> <Key> <Column name='the_year'/> <Column name='quarter'/> </Key> </Attribute> <Attribute name='Month' levelType='TimeMonths' nameColumn='the_month' hasHierarchy='false'> <Key> <Column name='the_year'/> <Column name='month_of_year'/> </Key> </Attribute> <Attribute name='Time Id' keyColumn='time_id' hasHierarchy='false'/> </Attributes> <Hierarchies> <Hierarchy name='Time' hasAll='false'> <Level attribute='Year'/> <Level attribute='Quarter'/> <Level attribute='Month'/> </Hierarchy> </Hierarchies></Dimension>Encodage des références de colonnes
Deux conventions d’encodage apparaissent dans le format YAML :
Références qualifiées table.column — à l’intérieur des listes key et name_columns, une colonne appartenant à une table non par défaut est écrite comme "table_name.column_name". Le convertisseur sépare au premier . pour récupérer la table et la colonne :
key: - "product_class.product_family" - "product_class.product_department" - "brand_name" # unqualified — belongs to the default tableTokens {col:...} — à l’intérieur des corps d’expressions SQL pour les colonnes calculées, les références de colonnes inline utilisent {col:column_name} ou {col:table.column_name}. Le convertisseur les reparse en éléments <Column> :
expression: mysql: "CONCAT({col:fname}, ' ', {col:lname})" generic: "{col:fullname}"Limitations connues
-
Identifiants contenant un point littéral — l’encodage
table.columnsépare au premier caractère.. Les noms de table ou de colonne contenant un.(par ex. identifiants entre guillemets) ne font pas un round-trip correct. Idem pour les tokens{col:table.column}. -
Whitespace dans le SQL des colonnes calculées — le sérialiseur YAML peut introduire des whitespaces de tête supplémentaires sur les corps SQL quand un fichier a été généré automatiquement et relu. Du SQL avec un whitespace de tête significatif peut acquérir une indentation supplémentaire à un second round-trip.
-
$refnécessite une URL de fichier — la résolution d’include$refne fonctionne que quand le schéma est chargé via une propriété de chaîne de connexionCatalog=file:///.... Les schémas chargés viaCatalogContentn’ont pas de répertoire de base et la résolution de$refest ignorée.
Connexe
- CLI — convertir entre XML et YAML et linter les schémas depuis la ligne de commande.
- Schema designer — générer un schéma XML à partir d’une description en langage naturel.
- Schemas — gérer et éditer les schémas sauvegardés dans le dashboard Saiku.