Aller au contenu

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"

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"

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. int provoque des appels ResultSet.getInt() et un stockage int). À 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.)

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"

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 :

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"

Attributs :

AttributRequisDescription
dimensionouiNom de la dimension à lier
foreignKeyColumnl’un de ceux-ciColonne FK unique dans la table de faits
<ForeignKey><Column/></ForeignKey>ou celui-ciÉlément enfant pour les clés étrangères composites
attributenonNom 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"

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

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"
AttributRequisDescription
dimensionouiNom de la dimension qui n’a pas de lien

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"
AttributRequisDescription
dimensionouiNom 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é.

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"
AttributRequisDescription
dimensionouiLa dimension liée indirectement
viaDimensionnonLa dimension intermédiaire dont l’attribut sert de bridge
viaAttributenonL’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.

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"
AttributRequisDescription
dimensionouiLa dimension many-to-many à lier
bridgeTableouiTable physique mappant les lignes de faits aux membres de dimension
factForeignKeyColumnouiColonne de clé de granularité de fact sur la table de faits
bridgeFactKeyColumnouiColonne du bridge correspondant à factForeignKeyColumn
bridgeDimensionKeyColumnouiColonne du bridge correspondant à la clé de dimension
aggregationnonfullCount (défaut) ou weighted
weightColumnnonColonne 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éesType de hintValeurs permisesEffet
MySQLforce_indexNom d’un index sur la tableForce 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"

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"

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