Pular para o conteúdo

Dimensões, atributos e hierarquias

Uma dimensão é um agrupamento de atributos relacionados — os eixos pelos quais você segmenta em uma consulta. A dimensão [Customer] pode conter [Gender], [City] e [Country]; a dimensão [Time] contém [Year], [Quarter], [Month] e [Day]. Esta página cobre cada aspecto da autoria de dimensões no Mondrian 4, da dimensão mais simples de uma tabela a joins snowflake, funções de tempo, member properties e dicas de otimização SQL.

Dimensões e atributos

Uma dimensão é declarada com um elemento <Dimension>. Ela sempre tem:

  • um name — o nome MDX usado em consultas
  • uma table — a tabela (ou alias) do banco da qual a dimensão é tirada
  • uma key — o nome do atributo que identifica unicamente cada linha
Customer:
table: "customer"
key: "Id"
attributes:
- name: "Gender"
- name: "Id"

Cada <Attribute> dentro de <Attributes> se torna consultável independentemente em MDX. O Mondrian gera automaticamente uma hierarquia de um único level para cada atributo (veja Hierarquias de atributo abaixo), para que você possa começar a usar atributos em consultas sem definir nenhum elemento <Hierarchy> explicitamente.

Chave de dimensão

Toda dimensão precisa de um atributo chave — o atributo cujos valores identificam unicamente cada linha na tabela de dimensão. Você o declara com o atributo key em <Dimension>, que deve casar com o name de um dos atributos em <Attributes>.

Por exemplo, na dimensão [Customer] acima, key="Id" aponta para <Attribute name="Id" column="customer_id"/>. O atributo chave é usado ao ligar a dimensão a uma tabela de fato via <ForeignKeyLink>.

Chave e nome de atributo

A chave de um atributo é a coluna (ou colunas) que identifica unicamente um membro. Por default, o nome do atributo — a string mostrada aos usuários — também é a chave. Você pode sobrescrever isso:

attributes:
- name: "Month"
key:
- "the_year"
- "month"
name_column: "month_name"
order_by_column: "month"

Conceitos chave:

  • column (ou um bloco aninhado <Key>/<Column>) — a(s) coluna(s) que identifica(m) o membro. Uma chave composta garante que membros em dois anos diferentes que compartilham o mesmo nome (ex.: Q1) sejam tratados como membros separados.
  • nameColumn — a coluna para exibir. Se omitida, a chave (última coluna numa chave composta) é usada.
  • orderByColumn — a coluna controlando a ordem de classificação. Se omitida, os membros são ordenados por nome.
  • captionColumn — a coluna para o caption, se diferente do nome.

Ordem de atributos

Por default, atributos são ordenados pelo nome. Isso nem sempre é o que você quer. Considere o atributo [Month] — se nomes de mês são armazenados como strings, ordem alfabética dá April, August, December… em vez de January, February, March…

Conserte isso definindo orderByColumn para a coluna numérica do mês:

attributes:
- name: "Month"
key:
- "the_year"
- "month"
name_column: "month_name"
order_by_column: "month"

Com orderByColumn="month" apontando para a coluna numérica 1..12, o Mondrian ordena pelo número mas exibe o nome legível.

Hierarquias e levels

Alguns atributos são naturalmente usados juntos. Um usuário de negócio vendo um estado frequentemente quer expandi-lo para ver cidades. Vendo um mês, ele pode querer rolar para quarter ou ano. Para tais combinações, você define uma hierarquia.

Uma hierarquia é uma lista ordenada de atributos — mais grossa no topo, mais fina embaixo. Cada entrada na hierarquia é um level.

Time:
table: "time_by_day"
key: "Day"
attributes:
- name: "Year"
- name: "Quarter"
key:
- "the_year"
- "quarter"
- name: "Month"
key:
- "the_year"
- "month_of_year"
- name: "Week"
key:
- "the_year"
- "week_of_year"
- name: "Day"
hierarchies:
- name: "Yearly"
has_all: false
levels:
- "Year"
- "Quarter"
- "Month"
- "Day"
- name: "Weekly"
has_all: false
levels:
- "Year"
- "Week"
- "Day"

Cada <Level attribute="..."/> referencia um atributo pelo nome. As definições de atributo fazem a maior parte do trabalho; a hierarquia é só uma declaração de quais atributos você quer e em que ordem.

Projetar atributos para uso em hierarquias

A regra chave para hierarquias: cada atributo deve ser funcionalmente dependente do atributo do level abaixo dele. Isso significa que deve haver exatamente um Quarter para qualquer Month dado, e exatamente um Year para qualquer Quarter dado.

Uma hierarquia Year → Month → Week → Day violaria essa regra, porque alguns dias na Semana 5 pertencem a January e alguns a February.

A consequência prática é que a maioria dos atributos em uma hierarquia precisa de chaves compostas para capturar a relação pai-filho. Se seu atributo Quarter tem apenas 4 membros (porque você esqueceu de incluir the_year em sua chave), a sequência de level será 10, 4, 120, 3652 — uma sequência não crescente que sinaliza um erro de modelagem.

Ordem e exibição de levels

O atributo orderByColumn em <Attribute> controla como membros dentro de um level são ordenados. O nameColumn controla a string de exibição. Esses dois atributos são independentes: você pode ordenar por uma coluna numérica e exibir uma coluna de nome legível.

Colunas ordinais podem ser de qualquer tipo de dado que possa ser usado em uma cláusula ORDER BY. A ordenação é escopada por-pai — uma coluna day_in_month cicla de 1 a 28–31 dentro de cada mês.

O atributo type em <Attribute> (valores: String, Integer, Numeric, Boolean, Date, Time, Timestamp) diz ao Mondrian como gerar SQL para a chave daquele atributo. O default é Numeric. Se a chave é uma string, o Mondrian precisa saber para que possa envolver valores em aspas simples:

WHERE productSku = '123-455-AA'

Membros ‘All’ e default

Por default, cada hierarquia contém um level no topo chamado (All), que segura um único membro chamado (All {hierarchyName}). Esse membro é o pai de todos os outros membros e representa um grand total. Também é o membro default — o membro usado quando a hierarquia está ausente dos eixos da consulta.

Você pode customizar esse comportamento com atributos em <Hierarchy>:

AtributoDescrição
hasAllSe o level (All) existe. Default true.
allMemberNameNome do membro all. Default "All {hierarchyName}".
allLevelNameNome do level all. Default "(All)".
defaultMemberNome MDX totalmente qualificado do membro default.
<Hierarchy name="Yearly" hasAll="false" defaultMember="[Time].[1997].[Q1].[1]">
...
</Hierarchy>

Quando hasAll="false", o membro default vira o primeiro membro do primeiro level — para uma hierarquia Time, o primeiro ano nos dados. Isso pode causar resultados inesperados quando aquela hierarquia não está em um eixo, então prefira hasAll="true" a menos que tenha uma razão específica.

Quando defaultMember é definido, pode até ser um calculated member.

Hierarquias de atributo

MDX não sabe de atributos — só sabe de dimensões, hierarquias, levels e membros. O Mondrian preenche o gap gerando automaticamente uma hierarquia de um único level para cada atributo, chamada hierarquia de atributo.

Hierarquias de atributo funcionam exatamente como hierarquias declaradas manualmente. Elas deixam você expor uma dúzia de atributos e começar a consultá-los direto, sem escrever quaisquer elementos <Hierarchy>.

Para controlar uma hierarquia de atributo, use os seguintes atributos em <Attribute>:

Atributo de <Hierarchy>Atributo de <Attribute>Descrição
N/AhasHierarchySe uma hierarquia de atributo é gerada. Default: true.
nameN/ASempre igual ao nome do atributo.
hasAllhierarchyHasAllSe a hierarquia tem um level (All). Default: true.
allMemberNamehierarchyAllMemberNameNome do membro all.
allMemberCaptionhierarchyAllMemberCaptionCaption do membro all.
allLevelNamehierarchyAllLevelNameNome do level all.
defaultMemberhierarchyDefaultMemberNome MDX totalmente qualificado do membro default.

Atributos versus hierarquias

No Mondrian 3, hierarquias eram verbosas de definir e a sintaxe MDX era estranha quando uma dimensão tinha mais de uma hierarquia. Como resultado, a maioria dos schemas expunha dimensões como uma única hierarquia e tratava levels individuais como a unidade de análise.

O Mondrian 4 encoraja uma abordagem diferente: projete com muitos atributos, adicione hierarquias só onde forem úteis.

  • Defina atributos para cada coluna pela qual você quer segmentar.
  • Deixe os usuários explorarem o cubo.
  • Quando você notar que certas combinações de atributos são sempre usadas juntas (ex.: Year → Quarter → Month), crie uma hierarquia para esse caminho de drill.
  • Os usuários ainda usarão atributos isolados na maior parte do tempo.

Uma nuance: alguns atributos têm variantes within-parent e without-parent. Por exemplo, [Time].[Month] (120 membros ao longo de 10 anos) é diferente de [Time].[Month of Year] (12 membros). O primeiro deixa você comparar December 2012 com December 2011; o segundo deixa você comparar December com April em todos os anos. Você precisa de dois atributos separados. Uma convenção de nomenclatura como "X of Parent" ajuda usuários a entenderem qual é qual.

Atalhos de schema

XML pode ser verboso. O Mondrian fornece atalhos para manter coisas simples concisas.

Atributo como atalho para um elemento aninhado singleton

Quando um atributo tem uma chave de coluna única, você pode escrever:

<Attribute name="A" column="c"/>

em vez da forma mais longa:

<Attribute name="A">
<Key>
<Column name="c"/>
</Key>
</Attribute>

Quando uma chave composta ou referência de coluna cross-table é necessária, use a forma aninhada <Key>.

O mesmo padrão de atalho se aplica em todo o schema:

Elemento paiAtributo shorthandElemento aninhado equivalenteDescrição
<Attribute>keyColumn<Key>Coluna(s) que compreendem a chave deste atributo.
<Attribute>nameColumn<Name>Coluna mostrada como o nome do membro. Default: a chave.
<Attribute>orderByColumn<OrderBy>Coluna(s) que definem a ordem de classificação. Default: a chave.
<Attribute>captionColumn<Caption>Coluna que forma o caption. Default: o nome.
<Measure>column<Arguments>Coluna(s) passadas à função agregada SQL.
<Table>keyColumn<Key>Coluna(s) que formam a primary key da tabela.
<Link>foreignKeyColumn<ForeignKey>Coluna(s) que formam a foreign key da tabela referenciante de um link.
<ForeignKeyLink>foreignKeyColumn<ForeignKey>Coluna(s) ligando a tabela de fato de um measure group a uma tabela de dimensão.

Atributo table herdado

O atributo table em <Dimension>, <Attribute> e <Column> é herdado do elemento envolvente quando não definido explicitamente. Isso torna dimensões de tabela única concisas — declare table uma vez em <Dimension> e cada <Attribute> dentro o herda automaticamente.

Shared dimensions

Se vários cubos no mesmo schema usam dimensões com a mesma definição, defina uma shared dimension no nível do schema e referencie-a de cada cubo.

A dimensão Measures

Medidas são tratadas como membros de uma dimensão especial chamada Measures. Tem uma única hierarquia e um único level. Como há só uma hierarquia, MDX deixa você omitir o nome da hierarquia:

[Measures].[Unit Sales]

é shorthand para:

[Measures].[Measures].[Unit Sales]

Esse design significa que você pode mudar o contexto de medida em um cálculo tão facilmente quanto muda um período de tempo ou uma região de vendas — habilita maior reuso de fórmulas e torna o controle de acesso mais simples (um grant em uma célula é uma coordenada tridimensional: cubo × fatia de dimensão × medida).

Dimensões estrela e snowflake

Dimensões estrela

As dimensões vistas até aqui tiram todas as suas colunas de uma única tabela. Estas são chamadas dimensões estrela porque irradiam da tabela de fato como pontos em uma estrela.

Dimensões snowflake

Uma dimensão snowflake abrange duas ou mais tabelas de dimensão unidas. Antes de definir uma, garanta:

  1. Toda tabela no snowflake é declarada no <PhysicalSchema>.
  2. Um elemento <Link> existe para cada join entre tabelas no snowflake.

Aqui está o par product e product_class usado para construir a dimensão [Product]:

tables:
- name: "product"
key_column: "product_id"
- name: "product_class"
key_column: "product_class_id"
links:
- source: "product_class"
target: "product"
foreign_key_column: "product_class_id"

Então defina a dimensão, especificando overrides de table no nível do atributo onde necessário:

Product:
table: "product"
key: "Product Id"
attributes:
- name: "Product Family"
table: "product_class"
key_column: "product_family"
- name: "Product Department"
table: "product_class"
key:
- "product_family"
- "product_department"
- name: "Brand Name"
table: "product_class"
key:
- "product_family"
- "product_department"
- "product_class.brand_name"
- name: "Product Name"
table: "product"
key_column: "product_id"
name_column: "product_name"
- name: "Product Id"
table: "product"
key_column: "product_id"

O atributo table cascateia: <Dimension table="product"> define o default, <Attribute table="product_class"> sobrescreve, e <Column table="product_class"> sobrescreve novamente. O Mondrian reportará um erro se não houver caminho entre as tabelas, ou se houver mais de um caminho.

Dimensões de tempo

MDX inclui funções cientes de tempo — ParallelPeriod, PeriodsToDate, WTD, MTD, QTD, YTD, LastPeriod — que só funcionam corretamente quando o Mondrian sabe quais atributos representam períodos de tempo.

Declare uma dimensão de tempo adicionando type="TimeDimension" a <Dimension>. Depois marque cada atributo com um valor de levelType:

Valor de levelTypeSignificado
TimeYearsAno
TimeHalfYearMeio-ano
TimeQuartersQuarter
TimeMonthsMês
TimeWeeksSemana
TimeDaysDia
TimeHoursHora
TimeMinutesMinuto
TimeSecondsSegundo

Uma dimensão de tempo completa se parece com isso:

<Dimension name="Time" table="time_by_day" key="Day" type="TimeDimension">
<Attributes>
<Attribute name="Year" keyColumn="the_year" levelType="TimeYears"/>
<Attribute name="Quarter" levelType="TimeQuarters">
<Key>
<Column name="the_year"/>
<Column name="quarter"/>
</Key>
</Attribute>
<Attribute name="Month" levelType="TimeMonths" nameColumn="month_name" orderByColumn="month_of_year">
<Key>
<Column name="the_year"/>
<Column name="month_of_year"/>
</Key>
</Attribute>
<Attribute name="Week" levelType="TimeWeeks">
<Key>
<Column name="the_year"/>
<Column name="week_of_year"/>
</Key>
</Attribute>
<Attribute name="Day" keyColumn="time_id" levelType="TimeDays"/>
</Attributes>
<Hierarchies>
<Hierarchy name="Yearly" hasAll="true" allMemberName="All Periods">
<Level attribute="Year"/>
<Level attribute="Quarter"/>
<Level attribute="Month"/>
<Level attribute="Day"/>
</Hierarchy>
</Hierarchies>
</Dimension>

Atributos tier e duration

Dois formatos de atributo são computados a partir das colunas subjacentes em vez de lidos direto de uma coluna chave. Ambos são extensões do Saiku ao Mondrian 4 (issue #108): eles dessugar para uma expressão SQL por-dialect renderizada pelo backend Calcite, então você obtém os membros binned/derivados sem modelar uma tabela helper ou view.

Tier (binning)

Um <Tier> transforma uma coluna numérica em um pequeno conjunto de bins ordenados e nomeados — o equivalente nativo de schema ao type: tier do LookML. Cada bin exceto o último carrega um boundary numérico (seu limite superior exclusivo); uma linha pega o rótulo do primeiro bin cujo boundary seu valor é estritamente menor que. O bin final omite boundary e captura tudo a partir do último bound. Membros ordenam por ordem de boundary, não lexicalmente.

attributes:
- name: "Size Tier"
tier:
column: "units"
bins:
- boundary: 10
label: "Small" # units < 10
- boundary: 100
label: "Medium" # 10 ≤ units < 100
- label: "Large" # units ≥ 100 (open-ended)

column é obrigatório; um table opcional seleciona a tabela fonte quando não é a própria tabela do atributo (ou da dimensão).

Duration

Um <Duration> computa um intervalo numérico entre duas colunas date/timestamp em uma unit fixa — o equivalente ao dimension_group: { type: duration } do LookML. Os membros são números e ordenam numericamente.

attributes:
- name: "Lead Time (months)"
duration:
start_column: "order_date"
end_column: "ship_date"
unit: "MONTH"

startColumn e endColumn são obrigatórios; unit é um de DAY (o default), WEEK, MONTH, QUARTER, YEAR, HOUR, MINUTE, SECOND. Um table opcional seleciona a tabela fonte.

Member properties

Member properties anexam informação extra a membros de um atributo — dados relacionados ao atributo mas não usados como chave ou para agrupamento. Você os declara usando <Property> dentro de <Attribute>:

<Attribute name="City" keyColumn="city_id">
<Property attribute="Country"/>
<Property attribute="State"/>
<Property attribute="City Population" name="Population"/>
</Attribute>

Aqui, o atributo [City] ganha três properties:

  • Country e State herdam o nome do atributo referenciado.
  • City Population é referenciado pelo nome do atributo mas exposto como Population via o override explícito de name.

Properties são definidas em termos de outros atributos na mesma dimensão. Isso significa que cada property tem uma chave, um nome, um caption e uma ordem de classificação — como qualquer atributo. O atributo referenciado deve ser funcionalmente dependente do atributo sendo anotado. Uma property baseada em [Zipcode] em [City] seria ilegal — uma cidade pode ter múltiplos CEPs. Mas cada cidade tem exatamente um estado, um país e um valor de população.

Você pode acessar properties em MDX via:

member.Properties("propertyName")

Por exemplo:

SELECT {[Measures].[Store Sales]} ON COLUMNS,
TopCount(
Filter(
[Customer].[City].Members,
[Customer].[City].CurrentMember.Properties("Population") < 10000),
10,
[Measures].[Store Sales]) ON ROWS
FROM [Sales]

O Mondrian infere o tipo da property pelo atributo type da definição <Property> (String, Numeric ou Boolean) quando o nome da property é uma string constante. Se você constrói o nome da property dinamicamente com uma expressão, o Mondrian retorna um valor sem tipo.

Dimensões degeneradas

Uma dimensão degenerada é uma tão simples que não justifica sua própria tabela de dimensão. Considere uma coluna payment_method (valores: Credit, Cash, ATM) sentada diretamente na tabela de fato. Criar uma tabela de lookup separada de três linhas só para esses valores adiciona um join sem benefício.

Em vez disso, declare uma dimensão sem especificar uma table, e o Mondrian lê as colunas diretamente da tabela de fato. No M4 você pode fazer isso omitindo o atributo table em <Dimension> e garantindo que a column do atributo exista na tabela de fato:

Payment Method:
key: "Payment Method"
attributes:
- name: "Payment Method"
key_column: "payment_method"

Como não há join, você não precisa de uma coluna de foreign key <ForeignKeyLink> — a coluna payment_method já está na tabela de fato. Elementos <Link> também não são necessários.

Cardinalidade aproximada de level

O atributo approxRowCount em <Attribute> (e em <Level> dentro de hierarquias explícitas) diz ao Mondrian aproximadamente quantos membros distintos este atributo tem. Fornecer essa dica pode melhorar significativamente a performance reduzindo a necessidade de o Mondrian executar consultas COUNT(DISTINCT ...) para determinar cardinalidade — particularmente perceptível ao conectar via XMLA.

<Attribute name="Product Name" keyColumn="product_id" approxRowCount="1560"/>

Atributo de medida default

O atributo defaultMeasure em <Cube> deixa você especificar explicitamente qual medida é selecionada quando uma consulta não referencia a dimensão [Measures]. Sem ele, o Mondrian pega a primeira medida declarada no cubo.

Definir defaultMeasure é especialmente útil quando você quer um calculated member seja o default, já que calculated members são declarados após as medidas base:

<Cube name="Sales" defaultMeasure="Unit Sales">
...
<CalculatedMember name="Profit" dimension="Measures">
<Formula>[Measures].[Store Sales] - [Measures].[Store Cost]</Formula>
...
</CalculatedMember>
</Cube>

Otimizações de dependência funcional

Quando o Mondrian gera SQL para popular membros de dimensão, usa GROUP BY para deduplicar linhas. Em alguns schemas você pode declarar que certas colunas são funcionalmente dependentes de outras — eliminando colunas redundantes de GROUP BY e melhorando a performance da consulta.

Dois atributos habilitam isso:

dependsOnLevelValue em <Property>

Definir dependsOnLevelValue="true" em uma property diz ao Mondrian que o valor da property é constante para qualquer valor de level dado. Por exemplo, uma planta de manufatura existe em exatamente uma cidade e um estado, então properties State e City são funcionalmente dependentes do level ManufacturingPlant.

uniqueKeyLevelName em <Hierarchy>

Definir uniqueKeyLevelName="Vehicle Identification Number" em uma hierarquia diz ao Mondrian que o level nomeado — junto com todos os levels acima dele — age como uma chave alternativa única. Para qualquer combinação única desses valores de level, há exatamente uma combinação de valores para todos os levels abaixo.

Exemplo:

<Dimension name="Automotive">
<Attributes>
<Attribute name="Make" keyColumn="make_id"/>
<Attribute name="Model" keyColumn="model_id"/>
<Attribute name="ManufacturingPlant" keyColumn="plant_id"/>
<Attribute name="Vehicle Identification Number" keyColumn="vehicle_id"/>
<Attribute name="LicensePlateNum" keyColumn="license_id"/>
</Attributes>
<Hierarchies>
<Hierarchy name="Automotive" hasAll="true" uniqueKeyLevelName="Vehicle Identification Number">
<Level attribute="Make"/>
<Level attribute="Model"/>
<Level attribute="ManufacturingPlant">
<Property attribute="State"/>
<Property attribute="City"/>
</Level>
<Level attribute="Vehicle Identification Number">
<Property attribute="Color"/>
<Property attribute="Trim"/>
</Level>
<Level attribute="LicensePlateNum">
<Property attribute="License State"/>
</Level>
</Hierarchy>
</Hierarchies>
</Dimension>

Quando o Mondrian consegue confirmar que:

  1. A consulta inclui o level unique-key, e
  2. Todas as properties na consulta têm dependsOnLevelValue="true"

…ele pode descartar a cláusula GROUP BY totalmente, o que é uma vitória substancial de performance em tabelas de dimensão grandes.

Em bancos que permitem colunas não agrupadas em SELECT (como MySQL), o Mondrian pode aplicar uma otimização parcial mesmo sem um uniqueKeyLevelName — deixando properties funcionalmente dependentes fora do GROUP BY enquanto mantém as colunas não dependentes nele.


Adaptado do guia de schema do projeto Mondrian, disponível sob a Eclipse Public License v1.0.