Schéma physique
Le schéma physique est l’endroit où vous informez Mondrian des objets de base de données qui se trouvent sous vos cubes : quelles tables existent, comment leurs colonnes sont typées, comment les tables sont liées entre elles et quels joins sont sûrs à traverser automatiquement. Tout dans <PhysicalSchema> est purement une description structurelle — pas encore de sémantique analytique, juste la plomberie.
Table
Une table est une utilisation nommée d’une table de base de données. Vous la déclarez avec un élément <Table> à l’intérieur de <PhysicalSchema>. Le seul attribut requis est name ; si la table vit dans un schéma de base de données autre que celui par défaut, ajoutez l’attribut schema :
- name: "sales_fact_1997" schema: "Foodmart"<Table schema="Foodmart" name="sales_fact_1997"/>Clés primaires
Mondrian doit connaître la clé primaire de chaque table de dimension pour pouvoir joiner correctement. Vous pouvez la déclarer inline sur l’élément lui-même en utilisant keyColumn (colonne unique) ou avec un élément <Key> imbriqué (clé composite) :
# single-column shorthand- name: "product" key_column: "product_id"
# composite key- name: "time_by_day" key: - "the_year" - "quarter"<!-- single-column shorthand --><Table name="product" keyColumn="product_id"/>
<!-- composite key --><Table name="time_by_day"> <Key> <Column name="the_year"/> <Column name="quarter"/> </Key></Table>Les tables de faits n’ont pas besoin d’une déclaration <Key>.
Colonnes et colonnes calculées
À l’intérieur d’une <Table> vous pouvez optionnellement définir une section <ColumnDefs>. Si vous l’omettez, Mondrian lit les définitions de colonnes depuis JDBC — ce qui est adéquat pour la plupart des situations. Quand vous avez besoin d’un typage précis ou de colonnes calculées, déclarez-les explicitement :
<Table name="customer"> <ColumnDefs> <ColumnDef name="customer_id" type="Integer" internalType="int"/> <ColumnDef name="fname"/> <ColumnDef name="lname"/> <CalculatedColumnDef name="full_name" type="String"> <ExpressionView> <SQL dialect="mysql"> CONCAT(<Column name="fname"/>, ' ', <Column name="lname"/>) </SQL> <SQL dialect="generic"> <Column name="fullname"/> </SQL> </ExpressionView> </CalculatedColumnDef> </ColumnDefs></Table>(Cet exemple est montré uniquement en XML : le format YAML porte les
colonnes calculées mais pas les métadonnées de typage <ColumnDef> nues, et
il encode le SQL des colonnes calculées comme une chaîne par dialecte plutôt que
sous la forme d’élément <Column> imbriqué montrée ci-dessus. Une colonne
calculée dont le corps est du SQL pur — voir Colonnes calculées dans les
mesures —
fait un round-trip fidèle à travers YAML.)
<ColumnDef> déclare qu’une colonne physique existe et comment l’interpréter :
type— le type de données Mondrian (String,Integer,Numeric,Boolean,Date,Time,Timestamp). Cela contrôle l’ordre de tri et le type MDX des expressions construites à partir de la colonne.internalType— le type Java que Mondrian utilise pour stocker les valeurs en mémoire (par ex.intprovoque des appelsResultSet.getInt()et un stockageint). À utiliser avec parcimonie ; le défaut est généralement correct.
<CalculatedColumnDef> définit une colonne virtuelle comme expression SQL. Vous pouvez fournir des corps SQL par dialecte — Mondrian choisit le bon au moment de la requête, ce qui est inestimable lors de l’expédition d’un schéma qui doit fonctionner sur plusieurs backends de bases de données. À l’intérieur de chaque corps SQL, référencez les colonnes avec <Column name="..."/> (ou <Column table="..." name="..."/> pour les références cross-table) ; Mondrian les qualifie et les met entre guillemets de manière appropriée pour le dialecte.
Table inline
<InlineTable> vous permet d’embarquer un petit jeu de données de lookup directement dans le fichier de schéma — sans table de base de données requise. Vous déclarez les noms de colonnes et les types, puis listez les lignes. Mondrian matérialise cela comme s’il s’agissait d’une vraie table :
<Dimension name="Severity"> <Hierarchy hasAll="true" primaryKey="severity_id"> <InlineTable alias="severity"> <ColumnDefs> <ColumnDef name="id" type="Numeric"/> <ColumnDef name="desc" type="String"/> </ColumnDefs> <Rows> <Row> <Value column="id">1</Value> <Value column="desc">High</Value> </Row> <Row> <Value column="id">2</Value> <Value column="desc">Medium</Value> </Row> <Row> <Value column="id">3</Value> <Value column="desc">Low</Value> </Row> </Rows> </InlineTable> <Level name="Severity" column="id" nameColumn="desc" uniqueMembers="true"/> </Hierarchy></Dimension>Cela se comporte identiquement au fait d’avoir une table severity dans votre base de données avec trois lignes. Pour représenter une valeur NULL pour une cellule, omettez simplement l’élément <Value> pour cette colonne.
(Montré uniquement en XML — <InlineTable> ne fait pas partie du format de
schéma YAML. Créez les jeux de données de lookup inline en XML, ou fournissez les
lignes comme une vraie table de base de données.)
Query
Un élément <Query> définit une table virtuelle en enveloppant une instruction SQL — comme une vue inline. Vous lui donnez un nom pour que d’autres éléments du schéma puissent la référencer, et vous pouvez fournir du SQL par dialecte :
<Query name="american_customers"> <ExpressionView> <SQL dialect="generic"> SELECT * FROM customer WHERE country = 'USA' </SQL> </ExpressionView></Query>La query peut alors être utilisée partout où une <Table> est acceptée. C’est utile pour filtrer des lignes, pré-calculer des colonnes ou unioniser plusieurs tables avant que Mondrian ne les voie.
(Montré uniquement en XML. Dans Mondrian 4, une <Query> est identifiée par
un attribut alias — <Query alias="american_customers"> — plutôt que
name. Écrite avec alias, la query fait un round-trip à travers le
format YAML comme une entrée queries: portant l’expression
par dialecte.)
Link (joins table-à-table)
Un élément <Link> dans le schéma physique déclare une relation de join dirigée entre deux tables — l’équivalent physique d’une clé étrangère. Mondrian utilise les liens déclarés pour résoudre automatiquement les chemins de join quand vous construisez des dimensions snowflake.
Voici comment définir le lien entre une table emp et une table dept :
tables:- name: "emp" key_column: "empno"- name: "dept" key_column: "deptno"links:- source: "dept" target: "emp" foreign_key_column: "deptno"<Table name="emp" keyColumn="empno"/><Table name="dept" keyColumn="deptno"/><Link target="emp" source="dept" foreignKeyColumn="deptno"/>La table source contient la clé étrangère ; target est la table avec laquelle on joint. Vous pouvez aussi utiliser des enfants <ForeignKey><Column .../></ForeignKey> pour les clés étrangères composites au lieu de la forme abrégée foreignKeyColumn.
Important : les éléments physiques <Link> sont utilisés pour les chemins snowflake à l’intérieur des tables de dimension. Connecter la table de faits d’un measure group à ses tables de dimension nécessite un élément de lien de dimension explicite à l’intérieur de <DimensionLinks> — voir Liens de dimension dans les measure groups ci-dessous.
Liens de dimension dans les measure groups
Chaque <MeasureGroup> déclare comment elle se rapporte à chaque dimension du cube via un bloc <DimensionLinks>. Mondrian 4 supporte cinq types de liens standard, plus un <BridgeLink> spécifique à Saiku pour les relations many-to-many — six au total, chacun avec un objectif spécifique :
ForeignKeyLink
Le join standard : la table de faits a une colonne de clé étrangère pointant vers l’attribut clé de la dimension. Cela couvre la grande majorité des conceptions de cubes en schéma étoile.
- type: "foreign_key" dimension: "Store" foreign_key_column: "store_id"<ForeignKeyLink dimension="Store" foreignKeyColumn="store_id"/>Attributs :
| Attribut | Requis | Description |
|---|---|---|
dimension | oui | Nom de la dimension à lier |
foreignKeyColumn | l’un de ceux-ci | Colonne FK unique dans la table de faits |
<ForeignKey><Column/></ForeignKey> | ou celui-ci | Élément enfant pour les clés étrangères composites |
attribute | non | Nom de l’attribut cible quand la FK ne pointe pas vers la clé de la dimension |
Exemple avec une FK composite et un attribut cible explicite :
- type: "foreign_key" dimension: "Time" foreign_key: - "time_id" attribute: "Date"<ForeignKeyLink dimension="Time" attribute="Date"> <ForeignKey> <Column name="time_id"/> </ForeignKey></ForeignKeyLink>CopyLink
Utilisé uniquement dans les measure groups agrégés (type="aggregate"). La table agrégée contient déjà des colonnes de clé de dimension pré-roulées — aucun join n’est nécessaire car les données de dimension ont été copiées dans la table agrégée. Vous déclarez quelles colonnes de dimension correspondent à quelles colonnes de la table agrégée :
<CopyLink dimension="Time" attribute="Month"> <Column table="time_by_day" name="the_year" aggColumn="time_year"/> <Column table="time_by_day" name="quarter" aggColumn="quarter"/> <Column table="time_by_day" name="month_of_year" aggColumn="month_of_year"/></CopyLink>Chaque enfant <Column> mappe une colonne de dimension (table + name) à sa colonne homologue dans la table agrégée (aggColumn). L’attribut attribute sur <CopyLink> est un no-op dans Mondrian et n’affecte pas le comportement de requête.
(Montré uniquement en XML. Les mappings de colonnes d’un copy link font
un round-trip à travers YAML sous forme de liste column_refs:, mais le
attribute no-op est abandonné par le convertisseur YAML — donc pour
éviter de montrer un jumeau YAML qui a silencieusement perdu un attribut
que la source XML porte encore, ce bloc est laissé en XML.)
NoLink
Déclare explicitement que ce measure group n’est pas en relation avec la dimension nommée. Mondrian retourne NULL pour les mesures de ce groupe quand une requête filtre par la dimension non liée. L’utilisation de <NoLink> est recommandée (au lieu d’omettre l’entrée) quand l’attribut de schéma missingLink est défini à warning — ce qui est la valeur par défaut.
- type: "no_link" dimension: "Warehouse"<NoLink dimension="Warehouse"/>| Attribut | Requis | Description |
|---|---|---|
dimension | oui | Nom de la dimension qui n’a pas de lien |
FactLink
Déclare que la table de dimension et la table de faits sont la même table physique — ce qu’on appelle parfois une dimension dégénérée. Aucun join n’est généré ; les colonnes de dimension sont lues directement à partir des lignes de la table de faits.
- type: "fact" dimension: "Store Type"<FactLink dimension="Store Type"/>| Attribut | Requis | Description |
|---|---|---|
dimension | oui | Nom de la dimension dégénérée |
C’est courant pour les dimensions comme « Has coffee bar » ou « Payment method » qui sont stockées comme colonnes sur la table de faits plutôt que dans un lookup séparé.
ReferenceLink
Déclare que la dimension est atteinte indirectement via l’attribut d’une autre dimension — un chemin bridge ou snowflake qui ne touche pas directement la table de faits. La FK joint à la clé d’un attribut spécifié d’une dimension intermédiaire spécifiée, pas à la table de faits.
- type: "reference" dimension: "Store" via_dimension: "Employee" via_attribute: "Store Id"<ReferenceLink dimension="Store" viaDimension="Employee" viaAttribute="Store Id"/>| Attribut | Requis | Description |
|---|---|---|
dimension | oui | La dimension liée indirectement |
viaDimension | non | La dimension intermédiaire dont l’attribut sert de bridge |
viaAttribute | non | L’attribut sur viaDimension qui contient la clé de join |
Cela apparaît dans le cube HR de FoodMart, où la dimension Store est atteinte via l’attribut Store Id de la dimension Employee plutôt que via une FK directe sur la table de faits salary.
BridgeLink
Une extension Saiku à Mondrian 4. Lie une dimension many-to-many à travers une table bridge séparée, pour qu’une seule ligne de faits puisse appartenir à plusieurs membres de dimension en même temps (un compte joint détenu par deux clients, un ticket avec plusieurs tags). Saiku résout le join fan-out en toute sécurité — fullCount déduplique le total via un agrégat symétrique, weighted divise chaque valeur par une colonne d’allocation.
dimension_links:- type: "bridge" dimension: "Customer" bridge_table: "account_owner" fact_foreign_key_column: "account_id" bridge_fact_key_column: "account_id" bridge_dimension_key_column: "customer_id" aggregation: "weighted" weight_column: "weight"<BridgeLink dimension="Customer" bridgeTable="account_owner" factForeignKeyColumn="account_id" bridgeFactKeyColumn="account_id" bridgeDimensionKeyColumn="customer_id" aggregation="weighted" weightColumn="weight"/>| Attribut | Requis | Description |
|---|---|---|
dimension | oui | La dimension many-to-many à lier |
bridgeTable | oui | Table physique mappant les lignes de faits aux membres de dimension |
factForeignKeyColumn | oui | Colonne de clé de granularité de fact sur la table de faits |
bridgeFactKeyColumn | oui | Colonne du bridge correspondant à factForeignKeyColumn |
bridgeDimensionKeyColumn | oui | Colonne du bridge correspondant à la clé de dimension |
aggregation | non | fullCount (défaut) ou weighted |
weightColumn | non | Colonne de poids d’allocation ; requise pour weighted |
La table de faits doit déclarer sa granularité comme une <Key>, et les requêtes bridge nécessitent le backend Calcite. Voir Dimensions bridge (many-to-many) pour le traitement complet, la sémantique d’allocation et un exemple détaillé.
Hints de table
Mondrian supporte un petit ensemble de hints d’optimiseur spécifiques à la base de données sur les éléments <Table>. Ceux-ci sont passés tels quels aux requêtes SQL générées :
| Base de données | Type de hint | Valeurs permises | Effet |
|---|---|---|---|
| MySQL | force_index | Nom d’un index sur la table | Force l’index nommé lors de la sélection des valeurs de niveau |
<Table name="automotive_dim"> <Hint type="force_index">my_index</Hint></Table>(Montré uniquement en XML — <Hint> ne fait pas partie du format YAML.
Exprimez les hints d’optimiseur en XML quand vous en avez besoin.)
Les hints sont optionnels et non portables. Ne les utilisez que quand le profilage montre un problème de plan spécifique que le hint corrige.
Schémas étoile et snowflake
La disposition de cube la plus simple — une table de faits jointe à plusieurs tables de dimension — s’appelle un schéma étoile. Chaque table de dimension se joint directement à la table de faits, et vous les connectez avec des entrées <ForeignKeyLink> dans le measure group.
Un schéma snowflake étend cela en permettant à une dimension de s’étendre sur plusieurs tables. Au lieu d’une seule table de dimension, vous avez une chaîne : la table de faits joint à la première table de dimension, qui joint à une seconde, et ainsi de suite. Dans Mondrian 4, vous modélisez cela en déclarant des éléments physiques <Link> entre les tables de dimension dans <PhysicalSchema>, et Mondrian résout le chemin de join automatiquement. Les tables de dimension snowflake sont référencées par les éléments <Attribute> via leur attribut table.
Par exemple, une dimension Product s’étendant sur les tables product et product_class nécessite :
physical_schema: tables: - name: "product" key_column: "product_id" - name: "product_class" key_column: "product_class_id" links: - source: "product" target: "product_class" foreign_key_column: "product_class_id"shared_dimensions: Product: table: "product" key: "Product Id" attributes: - name: "Product Id" table: "product" key_column: "product_id" has_hierarchy: false - name: "Product Name" table: "product" key_column: "product_id" - name: "Product Category" table: "product_class" key_column: "product_class_id"<!-- Physical schema links --><Table name="product" keyColumn="product_id"/><Table name="product_class" keyColumn="product_class_id"/><Link target="product_class" source="product" foreignKeyColumn="product_class_id"/>
<!-- Dimension attributes referencing both tables --><Dimension name="Product" table="product" key="Product Id"> <Attributes> <Attribute name="Product Id" table="product" keyColumn="product_id"/> <Attribute name="Product Name" table="product" keyColumn="product_id"/> <Attribute name="Product Category" table="product_class" keyColumn="product_class_id"/> </Attributes></Dimension>Mondrian parcourt le graphe physique <Link> pour construire la bonne chaîne SQL JOIN. Vous n’avez pas besoin d’un élément <Join> à l’intérieur de la définition de dimension — c’était un pattern Mondrian 3. Voir Dimensions pour le modèle de dimension complet.
Dimensions partagées
Une dimension partagée est déclarée au niveau du schéma (en dehors de tout cube) et réutilisée par plusieurs cubes. Comme elle n’a pas de clé étrangère fixe, le lien est établi par cube à l’intérieur des <DimensionLinks> de chaque measure group.
Dans Mondrian 4, une dimension partagée apparaît dans le bloc <Dimensions> d’un cube avec un attribut source :
shared_dimensions: Store: table: "store" key: "Store Id" attributes: - name: "Store Id" key_column: "store_id" has_hierarchy: false - name: "Store Country" key_column: "store_country" has_hierarchy: false - name: "Store State" key_column: "store_state" has_hierarchy: false - name: "Store City" key_column: "store_city" has_hierarchy: false - name: "Store Name" key_column: "store_name"cubes: Sales: dimensions: - source: "Store" measure_groups: - name: "Sales" table: "sales_fact_1997" dimension_links: - type: "foreign_key" dimension: "Store" foreign_key_column: "store_id" Warehouse: dimensions: - source: "Store" measure_groups: - name: "Warehouse" table: "inventory_fact_1997" dimension_links: - type: "foreign_key" dimension: "Store" foreign_key_column: "warehouse_store_id"<!-- Shared dimension declared at schema level --><Dimension name="Store" table="store" key="Store Id"> <Attributes> <Attribute name="Store Id" keyColumn="store_id"/> <Attribute name="Store Country" keyColumn="store_country" hasHierarchy="false"/> <Attribute name="Store State" keyColumn="store_state" hasHierarchy="false"/> <Attribute name="Store City" keyColumn="store_city" hasHierarchy="false"/> <Attribute name="Store Name" keyColumn="store_name"/> </Attributes></Dimension>
<!-- Cube 1 — Sales --><Cube name="Sales"> <Dimensions> <Dimension source="Store"/> <!-- reuse shared dimension --> </Dimensions> <MeasureGroups> <MeasureGroup name="Sales" table="sales_fact_1997"> <DimensionLinks> <ForeignKeyLink dimension="Store" foreignKeyColumn="store_id"/> </DimensionLinks> </MeasureGroup> </MeasureGroups></Cube>
<!-- Cube 2 — Warehouse --><Cube name="Warehouse"> <Dimensions> <Dimension source="Store"/> <!-- same shared dimension, different FK --> </Dimensions> <MeasureGroups> <MeasureGroup name="Warehouse" table="inventory_fact_1997"> <DimensionLinks> <ForeignKeyLink dimension="Store" foreignKeyColumn="warehouse_store_id"/> </DimensionLinks> </MeasureGroup> </MeasureGroups></Cube>La référence source="Store" tire les définitions complètes d’attributs et de hiérarchie de la dimension partagée. La colonne FK qui la connecte à la table de faits est déclarée sur le <ForeignKeyLink>, pas sur la dimension elle-même.
Note Mondrian 3 : M3 utilisait
<DimensionUsage source="..." foreignKey="..."/>pour référencer les dimensions partagées. Dans Mondrian 4, ceci est remplacé par<Dimension source="..."/>dans le bloc<Dimensions>du cube et un<ForeignKeyLink>dans le measure group. Voir Dimensions pour le modèle de dimension complet basé sur les attributs.
Adapté du guide des schémas du projet Mondrian (EPL v1.0).