Pular para o conteúdo

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"

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"

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.: int causa chamadas ResultSet.getInt() e armazenamento int). 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.)

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"

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.

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:

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"

Atributos:

AtributoObrigatórioDescrição
dimensionsimNome da dimensão a ligar
foreignKeyColumnum destesColuna FK única na tabela de fato
<ForeignKey><Column/></ForeignKey>ou esteElemento filho para foreign keys compostas
attributenãoNome 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"

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

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"
AtributoObrigatórioDescrição
dimensionsimNome da dimensão que não tem link

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"
AtributoObrigatórioDescrição
dimensionsimNome 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.

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"
AtributoObrigatórioDescrição
dimensionsimA dimensão sendo ligada indiretamente
viaDimensionnãoA dimensão intermediária cujo atributo age como o bridge
viaAttributenãoO 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.

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"
AtributoObrigatórioDescrição
dimensionsimA dimensão many-to-many sendo ligada
bridgeTablesimTabela física mapeando linhas de fato a membros de dimensão
factForeignKeyColumnsimColuna chave de grão de fato na tabela de fato
bridgeFactKeyColumnsimColuna do bridge casando factForeignKeyColumn
bridgeDimensionKeyColumnsimColuna do bridge casando a chave da dimensão
aggregationnãofullCount (default) ou weighted
weightColumnnãoColuna 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:

BancoTipo de hintValores permitidosEfeito
MySQLforce_indexNome de um índice na tabelaForç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"

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"

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