Aller au contenu

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 → XML produit 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éRequiseNotes
nameouiNom d’affichage du schéma
metamodel_versionnonTypiquement "4.0"

Forme courte (nom uniquement) :

schema: FoodMart

Forme 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éRequiseNotes
nameouiNom de la table dans la base de données
aliasnonNom alternatif utilisé ailleurs dans le schéma (par ex. pour les self-joins)
schemanonQualificateur de schéma de base de données (par ex. dbo)
key_columnnonRaccourci de clé primaire à une seule colonne
keynonClé primaire multi-colonnes — liste de chaînes de noms de colonnes
calculated_columnsnonColonnes 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 declared

calculated_columns

Colonnes virtuelles calculées à partir d’expressions SQL. Mappe à <ColumnDefs><CalculatedColumnDef>.

CléRequiseNotes
nameouiNom de colonne utilisé ailleurs dans le schéma
typenonType Mondrian : String, Numeric, Integer
expressionouiMap 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.

Relations de clé étrangère entre tables. Chaque entrée mappe à un élément <Link source="..." target="...">.

CléRequiseNotes
sourceouiTable enfant (côté many)
targetouiTable parente (côté one)
foreign_key_columnnon*Nom de colonne FK unique (raccourci)
foreign_keynon*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éRequiseNotes
tablenonTable par défaut pour les attributs dans cette dimension
keynonNom de l’attribut clé (doit correspondre à un name d’attribut)
typenon"TIME" pour les dimensions temporelles ; omettre pour standard
attributesnonListe de définitions d’attributs
hierarchiesnonListe de définitions de hiérarchies
annotationsnonMap de nom d’annotation → texte

attributes

CléRequiseNotes
nameouiNom d’affichage de l’attribut
tablenonSurcharge la table par défaut de la dimension
key_columnnon*Clé à une seule colonne
keynon*Clé multi-colonnes — liste de chaînes "table.column" ou "column"
name_columnnonColonne utilisée pour le nom d’affichage du membre
name_columnsnonNom multi-colonnes — liste de chaînes de colonnes
order_by_columnnonColonne utilisée pour l’ordonnancement des membres
caption_columnnonColonne utilisée pour le caption du membre
level_typenonGranularité temporelle : TimeYears, TimeQuarters, TimeMonths, TimeWeeks, TimeDays
datatypenonBoolean, Numeric, Integer, String (le défaut String est omis)
has_hierarchynonfalse supprime la hiérarchie auto-générée à un seul attribut (défaut true ; omettre quand vrai)
hierarchy_all_member_namenonLabel du membre All pour la hiérarchie auto-générée
hierarchy_all_member_captionnonCaption du membre All pour la hiérarchie auto-générée
hierarchy_default_membernonNom unique MDX du membre par défaut pour la hiérarchie auto-générée
hierarchy_has_allnonfalse supprime le niveau All dans la hiérarchie auto-générée
propertiesnonListe de noms d’attributs frères qui sont des propriétés de cet attribut
annotationsnonMap 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: false

hierarchies

CléRequiseNotes
nameouiNom de la hiérarchie
all_member_namenonLabel pour le membre All
default_membernonNom unique MDX du membre par défaut
has_allnonfalse supprime le niveau All
levelsouiListe ordonnée de niveaux
annotationsnonMap 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éRequiseNotes
default_measurenonNom de la mesure par défaut
annotationsnonMap de nom d’annotation → texte
dimensionsnonListe d’utilisations de dimensions et définitions locales de dimensions
measure_groupsnonListe de définitions de measure groups
calculated_membersnonListe de définitions de membres calculés
named_setsnonListe 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éRequiseNotes
namenonNom du measure group ; optionnel pour le groupe principal
tableouiNom de la table de faits ou agrégée
typenon"aggregate" pour les measure groups agrégés ; omettre pour "fact"
approx_row_countnonIndice pour le nombre approximatif de lignes de la table de faits (chaîne, par ex. "86837")
ignore_unrelated_dimensionsnontrue traite les dimensions non liées comme [All] au lieu de retourner null
measuresnonListe de définitions de mesures ou de références de mesures
dimension_linksnonListe 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éRequiseNotes
nameouiNom d’affichage de la mesure
columnnonColonne source
aggregatorouisum, count, distinct-count, min, max, avg
format_stringnonChaîne de format MDX (par ex. "#,###.00", "Standard", "Currency")
datatypenonNumeric, Integer, String
propertiesnonListe de maps {name, value} ou {name, expression}
annotationsnonMap de nom d’annotation → texte

Référence de mesure (measure groups agrégés uniquement) :

CléRequiseNotes
refouiNom de la mesure référencée du groupe principal
agg_columnnonColonne 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"

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éRequiseNotes
typeoui"foreign_key"
dimensionouiNom de la dimension
foreign_key_columnnon*Nom de colonne FK unique
foreign_keynon*Liste de noms de colonnes FK (FK composée)
attributenonAttribut à 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éRequiseNotes
typeoui"copy"
dimensionouiNom de la dimension
column_refsnonListe de maps {table, name, agg_column}

no_link — ce measure group n’a pas de lien vers cette dimension :

CléRequiseNotes
typeoui"no_link"
dimensionouiNom de la dimension

fact — les données de la dimension viennent directement de la table de faits :

CléRequiseNotes
typeoui"fact"
dimensionouiNom de la dimension

reference — la dimension est atteinte indirectement via l’attribut d’une autre dimension :

CléRequiseNotes
typeoui"reference"
dimensionouiNom de la dimension
via_dimensionnonNom de la dimension intermédiaire
via_attributenonAttribut 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éRequiseNotes
nameouiNom du membre
dimensionnonDimension cible (par ex. "Measures")
hierarchynonNom unique MDX de la hiérarchie cible
parentnonNom unique MDX du membre parent
formulanonFormule MDX
format_stringnonChaîne de format MDX
captionnonCaption d’affichage montré dans les outils clients
descriptionnonDescription lisible par l’humain
visiblenonfalse masque le membre des outils clients (défaut true ; omettre quand vrai)
cell_formatternonCell formatter personnalisé — {class_name} ou {script: {language, body}}
propertiesnonListe de maps {name, value} ou {name, expression}
annotationsnonMap 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éRequiseNotes
nameouiNom du rôle
class_namenonClasse Java implémentant la logique de rôle personnalisée
schema_grantnonGrant 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"

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 table

Tokens {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

  1. Identifiants contenant un point littéral — l’encodage table.column sé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}.

  2. 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.

  3. $ref nécessite une URL de fichier — la résolution d’include $ref ne fonctionne que quand le schéma est chargé via une propriété de chaîne de connexion Catalog=file:///.... Les schémas chargés via CatalogContent n’ont pas de répertoire de base et la résolution de $ref est 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.