Schema físico
O schema físico é onde você conta ao Mondrian sobre os objetos de banco que ficam sob seus cubos: quais tabelas existem, como suas colunas são tipadas, como tabelas se relacionam umas com as outras e quais joins são seguros de atravessar automaticamente. Tudo em <PhysicalSchema> é descrição estrutural pura — ainda sem semântica analítica, apenas o encanamento.
Tabela
Uma tabela é um uso nomeado de uma tabela de banco. Você a declara com um elemento <Table> dentro de <PhysicalSchema>. O único atributo obrigatório é name; se a tabela vive em um schema de banco diferente do default, adicione o atributo schema:
- name: "sales_fact_1997" schema: "Foodmart"<Table schema="Foodmart" name="sales_fact_1997"/>Primary keys
O Mondrian precisa saber a primary key de cada tabela de dimensão para poder fazer join corretamente. Você pode declará-la inline no próprio elemento usando keyColumn (coluna única) ou com um elemento aninhado <Key> (chave composta):
# 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>Tabelas de fato não precisam de uma declaração <Key>.
Colunas e colunas calculadas
Dentro de uma <Table> você pode opcionalmente definir uma seção <ColumnDefs>. Se você omitir, o Mondrian lê definições de coluna do JDBC — o que é adequado para a maioria das situações. Quando você precisa de tipagem precisa ou colunas computadas, declare-as explicitamente:
<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>(Este exemplo é mostrado apenas em XML: o formato de schema YAML carrega colunas calculadas mas não metadados de tipagem de <ColumnDef> bare, e codifica SQL de coluna calculada como uma string por-dialect em vez da forma de elemento aninhado <Column> mostrada acima. Uma coluna calculada cujo body é SQL plano — veja Colunas calculadas em medidas — faz round-trip pelo YAML fielmente.)
<ColumnDef> declara que uma coluna física existe e como interpretá-la:
type— o tipo de dado Mondrian (String,Integer,Numeric,Boolean,Date,Time,Timestamp). Controla a ordem de classificação e o tipo MDX de expressões construídas a partir da coluna.internalType— o tipo Java que o Mondrian usa para armazenar valores em memória (ex.:intcausa chamadasResultSet.getInt()e armazenamentoint). Use com moderação; o default geralmente está correto.
<CalculatedColumnDef> define uma coluna virtual como uma expressão SQL. Você pode fornecer bodies SQL por-dialect — o Mondrian escolhe o certo no momento da consulta, o que é inestimável ao distribuir um schema que precisa rodar em múltiplos backends de banco. Dentro de cada body SQL, referencie colunas com <Column name="..."/> (ou <Column table="..." name="..."/> para referências cross-table); o Mondrian as qualifica e quota apropriadamente para o dialeto.
Inline table
<InlineTable> deixa você embutir um pequeno dataset de lookup diretamente no arquivo de schema — sem tabela de banco necessária. Você declara os nomes e tipos das colunas, depois lista as linhas. O Mondrian materializa isso como se fosse uma tabela real:
<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>Isso se comporta identicamente a ter uma tabela severity no seu banco com três linhas. Para representar um valor NULL para uma célula, simplesmente omita o elemento <Value> para aquela coluna.
(Mostrado apenas em XML — <InlineTable> não faz parte do formato de schema YAML. Escreva datasets de lookup inline em XML, ou forneça as linhas como uma tabela de banco real.)
Query
Um elemento <Query> define uma tabela virtual envolvendo um statement SQL — como uma view inline. Você dá um nome para que outros elementos de schema possam referenciá-la, e pode fornecer SQL por-dialect:
<Query name="american_customers"> <ExpressionView> <SQL dialect="generic"> SELECT * FROM customer WHERE country = 'USA' </SQL> </ExpressionView></Query>A query pode então ser usada onde quer que um <Table> seja aceito. Isso é útil para filtrar linhas, pré-computar colunas ou unir múltiplas tabelas antes do Mondrian vê-las.
(Mostrado apenas em XML. No Mondrian 4 um <Query> é identificado por um atributo alias — <Query alias="american_customers"> — em vez de name. Escrito com alias, a query faz round-trip pelo formato de schema YAML como uma entrada queries: carregando a expression por-dialect.)
Link (joins tabela-para-tabela)
Um elemento <Link> no schema físico declara uma relação de join direcionada entre duas tabelas — o equivalente físico de uma foreign key. O Mondrian usa links declarados para automaticamente resolver caminhos de join quando você constrói dimensões snowflake.
Aqui está como definir o link entre uma tabela emp e uma tabela 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"/>A tabela source segura a foreign key; target é a tabela à qual está sendo unida. Você também pode usar filhos <ForeignKey><Column .../></ForeignKey> para foreign keys compostas em vez do atalho foreignKeyColumn.
Importante: elementos físicos <Link> são usados para caminhos snowflake dentro de tabelas de dimensão. Conectar a tabela de fato de um measure group às suas tabelas de dimensão exige um elemento dimension link explícito dentro de <DimensionLinks> — veja Dimension links em measure groups abaixo.
Dimension links em measure groups
Todo <MeasureGroup> declara como se relaciona a cada dimensão no cubo através de um bloco <DimensionLinks>. O Mondrian 4 suporta cinco tipos de link padrão, mais um <BridgeLink> específico do Saiku para relações many-to-many — seis no total, cada um com um propósito específico:
ForeignKeyLink
O join padrão: a tabela de fato tem uma coluna de foreign key apontando para o atributo chave da dimensão. Isso cobre a vasta maioria de designs de cubo de schema estrela.
- type: "foreign_key" dimension: "Store" foreign_key_column: "store_id"<ForeignKeyLink dimension="Store" foreignKeyColumn="store_id"/>Atributos:
| Atributo | Obrigatório | Descrição |
|---|---|---|
dimension | sim | Nome da dimensão a ligar |
foreignKeyColumn | um destes | Coluna FK única na tabela de fato |
<ForeignKey><Column/></ForeignKey> | ou este | Elemento filho para foreign keys compostas |
attribute | não | Nome do atributo alvo quando a FK não aponta para a chave da dimensão |
Exemplo com FK composta e um atributo alvo explícito:
- type: "foreign_key" dimension: "Time" foreign_key: - "time_id" attribute: "Date"<ForeignKeyLink dimension="Time" attribute="Date"> <ForeignKey> <Column name="time_id"/> </ForeignKey></ForeignKeyLink>CopyLink
Usado apenas em measure groups agregados (type="aggregate"). A tabela agregada já contém colunas chave de dimensão pre-roladas — não há join necessário porque os dados de dimensão foram copiados para a tabela agregada. Você declara quais colunas de dimensão correspondem a quais colunas da tabela agregada:
<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>Cada filho <Column> mapeia uma coluna de dimensão (table + name) para sua coluna correspondente na tabela agregada (aggColumn). O atributo attribute em <CopyLink> é um no-op no Mondrian e não afeta o comportamento da consulta.
(Mostrado apenas em XML. Os mapeamentos de coluna de um copy link fazem round-trip pelo YAML como uma lista column_refs:, mas o no-op attribute é descartado pelo conversor YAML — então para evitar mostrar um gêmeo YAML que silenciosamente perdeu um atributo que o XML fonte ainda carrega, este bloco é deixado como XML.)
NoLink
Declara explicitamente que este measure group não se relaciona com a dimensão nomeada. O Mondrian retorna NULL para medidas neste grupo quando uma consulta filtra pela dimensão não ligada. Usar <NoLink> é recomendado (em vez de omitir a entrada) quando o atributo de schema missingLink é definido como warning — que é o default.
- type: "no_link" dimension: "Warehouse"<NoLink dimension="Warehouse"/>| Atributo | Obrigatório | Descrição |
|---|---|---|
dimension | sim | Nome da dimensão que não tem link |
FactLink
Declara que a tabela de dimensão e a tabela de fato são a mesma tabela física — o que às vezes é chamado de dimensão degenerada. Nenhum join é gerado; as colunas de dimensão são lidas diretamente das linhas da tabela de fato.
- type: "fact" dimension: "Store Type"<FactLink dimension="Store Type"/>| Atributo | Obrigatório | Descrição |
|---|---|---|
dimension | sim | Nome da dimensão degenerada |
Isso é comum para dimensões como “Has coffee bar” ou “Payment method” que são armazenadas como colunas na tabela de fato em vez de em uma lookup separada.
ReferenceLink
Declara que a dimensão é alcançada indiretamente através do atributo de outra dimensão — um caminho bridge ou snowflake que não toca a tabela de fato diretamente. A FK une à chave de um atributo especificado de uma dimensão intermediária especificada, não à tabela de fato.
- type: "reference" dimension: "Store" via_dimension: "Employee" via_attribute: "Store Id"<ReferenceLink dimension="Store" viaDimension="Employee" viaAttribute="Store Id"/>| Atributo | Obrigatório | Descrição |
|---|---|---|
dimension | sim | A dimensão sendo ligada indiretamente |
viaDimension | não | A dimensão intermediária cujo atributo age como o bridge |
viaAttribute | não | O atributo em viaDimension que segura a chave de join |
Isso aparece no cubo HR do FoodMart, onde a dimensão Store é alcançada via o atributo Store Id da dimensão Employee em vez de através de uma FK direta na tabela de fato salary.
BridgeLink
Uma extensão do Saiku ao Mondrian 4. Liga uma dimensão many-to-many através de uma tabela bridge separada, para que uma única linha de fato possa pertencer a vários membros de dimensão de uma vez (uma conta conjunta detida por dois clientes, um ticket com várias tags). O Saiku resolve o join fanned-out com segurança — fullCount deduplica o grand total via um agregado simétrico, weighted divide cada valor por uma coluna de alocação.
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"/>| Atributo | Obrigatório | Descrição |
|---|---|---|
dimension | sim | A dimensão many-to-many sendo ligada |
bridgeTable | sim | Tabela física mapeando linhas de fato a membros de dimensão |
factForeignKeyColumn | sim | Coluna chave de grão de fato na tabela de fato |
bridgeFactKeyColumn | sim | Coluna do bridge casando factForeignKeyColumn |
bridgeDimensionKeyColumn | sim | Coluna do bridge casando a chave da dimensão |
aggregation | não | fullCount (default) ou weighted |
weightColumn | não | Coluna de peso de alocação; obrigatório para weighted |
A tabela de fato deve declarar seu grão como uma <Key>, e consultas bridge exigem o backend Calcite. Veja Dimensões bridge (many-to-many) para o tratamento completo, semântica de alocação e um exemplo trabalhado.
Table hints
O Mondrian suporta um pequeno conjunto de dicas de otimizador específicas de banco em elementos <Table>. Estas são passadas para queries SQL geradas:
| Banco | Tipo de hint | Valores permitidos | Efeito |
|---|---|---|---|
| MySQL | force_index | Nome de um índice na tabela | Força o índice nomeado ao selecionar valores de level |
<Table name="automotive_dim"> <Hint type="force_index">my_index</Hint></Table>(Mostrado apenas em XML — <Hint> não faz parte do formato de schema YAML. Expresse dicas de otimizador em XML quando precisar delas.)
Hints são opcionais e não portáveis. Use-os apenas quando profiling mostra um problema de plano específico que o hint conserta.
Schemas estrela e snowflake
O layout de cubo mais simples — uma tabela de fato unida a várias tabelas de dimensão — é chamado de schema estrela. Cada tabela de dimensão se une diretamente à tabela de fato, e você as conecta com entradas <ForeignKeyLink> no measure group.
Um schema snowflake estende isso permitindo que uma dimensão abranja múltiplas tabelas. Em vez de uma única tabela de dimensão, você tem uma cadeia: a tabela de fato se une à primeira tabela de dimensão, que se une a uma segunda, e assim por diante. No Mondrian 4 você modela isso declarando elementos físicos <Link> entre as tabelas de dimensão em <PhysicalSchema>, e o Mondrian resolve o caminho de join automaticamente. Tabelas de dimensão snowflake são referenciadas de elementos <Attribute> usando seu atributo table.
Por exemplo, uma dimensão Product abrangendo tabelas product e product_class exige:
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>O Mondrian caminha pelo grafo físico <Link> para construir a cadeia SQL JOIN correta. Você não precisa de um elemento <Join> dentro da definição de dimensão — esse era um padrão do Mondrian 3. Veja Dimensões para o modelo completo de dimensão.
Shared dimensions
Uma shared dimension é declarada no nível do schema (fora de qualquer cubo) e reutilizada por múltiplos cubos. Como ela não tem foreign key fixa, o link é estabelecido por cubo dentro dos <DimensionLinks> de cada measure group.
No Mondrian 4, uma shared dimension aparece no bloco <Dimensions> de um cubo com um atributo 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>A referência source="Store" puxa as definições completas de atributo e hierarquia da shared dimension. A coluna FK que a conecta à tabela de fato é declarada no <ForeignKeyLink>, não na própria dimensão.
Nota Mondrian 3: M3 usava
<DimensionUsage source="..." foreignKey="..."/>para referenciar shared dimensions. No Mondrian 4 isso é substituído por<Dimension source="..."/>no bloco<Dimensions>do cubo e um<ForeignKeyLink>no measure group. Veja Dimensões para o modelo completo de dimensão baseado em atributo.
Adaptado do guia de schema do projeto Mondrian (EPL v1.0).