Pular para o conteúdo

Schemas YAML

Schemas XML são precisos mas são muitos colchetes angulares. Schemas YAML te dão o mesmo modelo em um formato que é mais fácil de ler à primeira vista, mais fácil de revisar em um pull request e fácil de anotar com comentários #. O engine do Saiku os trata identicamente — o conversor constrói o mesmo grafo de objetos tipado de qualquer maneira.

Por que YAML?

  • Legível. Menos caracteres para a mesma informação — uma dimensão típica que toma 60 linhas XML cabe em 20 linhas YAML.
  • Diffable. Mudanças estruturais (adicionar um level, renomear uma medida) produzem diffs limpos e legíveis em vez de diffs de sopa de atributo.
  • Comentável. Você pode anotar qualquer seção com comentários #; comentários XML são legais mas raramente sobrevivem round-trips por ferramentas gráficas.
  • Round-trippable. XML → YAML → XML produz resultados de consulta byte-equivalentes. Você pode converter seus schemas existentes a qualquer momento com a CLI.

Estrutura de nível superior

Um schema YAML M4 completo usa essas chaves de nível superior (apenas schema é obrigatório):

schema: # required — schema header
name: "FoodMart"
metamodel_version: "4.0"
annotations: # optional — schema-level metadata
caption.de_DE: "Verkaufen"
physical_schema: # optional — tables, calculated columns, links
tables: [...]
links: [...]
shared_dimensions: # optional — named dimensions reused across cubes
Store: { ... }
Time: { ... }
cubes: # optional — one entry per cube
Sales: { ... }
roles: # optional — access-control roles
- name: "California manager"
schema_grant: { ... }

schema (cabeçalho)

Mapeia para <Schema name="..." metamodelVersion="...">.

ChaveObrigatórioNotas
namesimNome de exibição do schema
metamodel_versionnãoTipicamente "4.0"

Forma curta (apenas nome):

schema: FoodMart

Forma longa:

schema:
name: "FoodMart"
metamodel_version: "4.0"

annotations

Um mapa plano de pares name: text. Suportado nos níveis de schema, cube, dimension, hierarchy, level, attribute, measure, calculated member e role. O Mondrian usa nomes qualificados com ponto como convenção para metadados específicos de locale:

annotations:
caption.de_DE: "Verkaufen"
caption.fr_FR: "Ventes"
description.fr_FR: "Cube des ventes"

physical_schema

Declara as tabelas físicas e as relações de foreign key entre elas.

tables

Uma lista de definições de tabela. Cada entrada mapeia para um elemento <Table>.

ChaveObrigatórioNotas
namesimNome da tabela no banco
aliasnãoNome alternativo usado em outro lugar no schema (ex.: para self-joins)
schemanãoQualificador de schema do banco (ex.: dbo)
key_columnnãoShorthand de primary key de coluna única
keynãoPrimary key multi-coluna — lista de strings de nome de coluna
calculated_columnsnãoColunas derivadas definidas como expressões SQL (veja abaixo)

key_column e key são mutuamente exclusivos. Use key_column para coluna única; use key para chaves compostas. Tabelas de fato tipicamente não têm chave declarada.

physical_schema:
tables:
- name: "customer"
key:
- "customer_id"
- name: "product"
key_column: "product_id"
- name: "salary"
alias: "salary2" # second alias for a self-join
- name: "sales_fact_1997" # fact table — no key declared

calculated_columns

Colunas virtuais computadas de expressões SQL. Mapeia para <ColumnDefs><CalculatedColumnDef>.

ChaveObrigatórioNotas
namesimNome de coluna usado em outro lugar no schema
typenãoTipo Mondrian: String, Numeric, Integer
expressionsimMapa de nome de dialeto SQL → body SQL

Referências de coluna inline dentro de bodies SQL usam tokens {col:column_name} ou {col:table.column_name}, que o conversor parseia de volta para elementos <Column>:

- name: "customer"
key:
- "customer_id"
calculated_columns:
- name: "full_name"
type: "String"
expression:
oracle: "{col:fname} || ' ' || {col:lname}"
mysql: "CONCAT({col:fname}, ' ', {col:lname})"
mssql: "{col:fname} + ' ' + {col:lname}"
generic: "{col:fullname}"

Chaves de dialeto suportadas incluem generic, mysql, oracle, postgres, mssql, access, derby, db2, luciddb.

Relações de foreign key entre tabelas. Cada entrada mapeia para um elemento <Link source="..." target="...">.

ChaveObrigatórioNotas
sourcesimTabela filha (lado many)
targetsimTabela pai (lado one)
foreign_key_columnnão*Nome da coluna FK única (shorthand)
foreign_keynão*Lista de nomes de coluna FK (FK composta)

*Um de foreign_key_column ou foreign_key é obrigatório.

links:
- source: "product_class"
target: "product"
foreign_key:
- "product_class_id"
- source: "store"
target: "employee"
foreign_key:
- "store_id"

shared_dimensions

Um mapa de dimension_name: dimension_body. A chave do mapa se torna o atributo name no elemento <Dimension> resultante. Shared dimensions vivem fora de qualquer cubo e podem ser referenciadas por múltiplos cubos.

ChaveObrigatórioNotas
tablenãoTabela default para atributos nesta dimensão
keynãoNome do atributo chave (deve casar com um name de atributo)
typenão"TIME" para dimensões de tempo; omita para padrão
attributesnãoLista de definições de atributo
hierarchiesnãoLista de definições de hierarquia
annotationsnãoMapa de nome de annotation → texto

attributes

ChaveObrigatórioNotas
namesimNome de exibição do atributo
tablenãoSobrescreve a tabela default da dimensão
key_columnnão*Chave de coluna única
keynão*Chave multi-coluna — lista de strings "table.column" ou "column"
name_columnnãoColuna usada para o nome de exibição do membro
name_columnsnãoNome multi-coluna — lista de strings de coluna
order_by_columnnãoColuna usada para ordenação de membro
caption_columnnãoColuna usada para o caption do membro
level_typenãoGrão de tempo: TimeYears, TimeQuarters, TimeMonths, TimeWeeks, TimeDays
datatypenãoBoolean, Numeric, Integer, String (default String é omitido)
has_hierarchynãofalse suprime a hierarquia de atributo único auto-gerada (default true; omita quando true)
hierarchy_all_member_namenãoRótulo do membro all para a hierarquia auto-gerada
hierarchy_all_member_captionnãoCaption do membro all para a hierarquia auto-gerada
hierarchy_default_membernãoNome único MDX do membro default para a hierarquia auto-gerada
hierarchy_has_allnãofalse suprime o level All na hierarquia auto-gerada
propertiesnãoLista de nomes de atributos irmãos que são properties deste atributo
annotationsnãoMapa de nome de annotation → texto

*key_column e key são mutuamente exclusivos. Para referências cross-table dentro de key ou name_columns, qualifique com table.column:

attributes:
- name: "Brand Name"
table: "product"
key:
- "product_class.product_family" # qualified cross-table ref
- "product_class.product_department"
- "brand_name" # unqualified — belongs to default table
name_column: "brand_name"
has_hierarchy: false

hierarchies

ChaveObrigatórioNotas
namesimNome da hierarquia
all_member_namenãoRótulo para o membro All
default_membernãoNome único MDX do membro default
has_allnãofalse suprime o level All
levelssimLista ordenada de levels
annotationsnãoMapa de nome de annotation → texto

Cada entrada em levels é uma string nua (quando o nome do level é igual ao nome do atributo) ou um mapa {name, attribute} (quando diferem):

hierarchies:
- name: "Stores"
all_member_name: "All Stores"
levels:
- "Store Country" # bare string
- "Store State"
- "Store City"
- "Store Name"
- name: "Education Level"
levels:
- name: "Education Level" # map form — level name differs from attribute
attribute: "Education"

Exemplo completo — a shared dimension Store do FoodMart:

shared_dimensions:
Store:
table: "store"
key: "Store Id"
attributes:
- name: "Store Country"
key_column: "store_country"
has_hierarchy: false
- name: "Store State"
key_column: "store_state"
has_hierarchy: false
- name: "Store City"
key:
- "store_state"
- "store_city"
name_column: "store_city"
has_hierarchy: false
- name: "Store Id"
key_column: "store_id"
has_hierarchy: false
- name: "Store Name"
key_column: "store_name"
has_hierarchy: false
properties:
- "Store Type"
- "Store Manager"
- name: "Store Type"
key_column: "store_type"
hierarchy_all_member_name: "All Store Types"
hierarchies:
- name: "Stores"
all_member_name: "All Stores"
levels:
- "Store Country"
- "Store State"
- "Store City"
- "Store Name"

cubes

Um mapa de cube_name: cube_body. A chave do mapa se torna o name do cubo.

ChaveObrigatórioNotas
default_measurenãoNome da medida default
annotationsnãoMapa de nome de annotation → texto
dimensionsnãoLista de usos de dimensão e definições de dimensão locais
measure_groupsnãoLista de definições de measure group
calculated_membersnãoLista de definições de calculated member
named_setsnãoLista de definições de named set

dimensions (nível de cubo)

Cada entrada é um uso (uma referência a uma shared dimension) ou uma definição local (uma dimensão inline definida apenas para este cubo).

Uso — um mapa com apenas source:

dimensions:
- source: "Store"
- source: "Time"
- source: "Product"

Definição local — um mapa com name mais o body completo da dimensão (mesmas chaves de shared_dimensions):

dimensions:
- name: "Customer"
table: "customer"
key: "Name"
attributes:
- name: "Country"
key_column: "country"
has_hierarchy: false
- name: "Name"
key_column: "customer_id"
name_column: "full_name"
order_by_column: "full_name"
has_hierarchy: false
hierarchies:
- name: "Customers"
all_member_name: "All Customers"
levels:
- "Country"
- "State Province"
- "City"
- "Name"

measure_groups

ChaveObrigatórioNotas
namenãoNome do measure group; opcional para o grupo primário
tablesimNome de tabela de fato ou agregada
typenão"aggregate" para measure groups agregados; omita para "fact"
approx_row_countnãoDica para a contagem aproximada de linhas da tabela de fato (string, ex.: "86837")
ignore_unrelated_dimensionsnãotrue trata dimensões não relacionadas como [All] em vez de retornar null
measuresnãoLista de definições de medida ou referência de medida
dimension_linksnãoLista de links deste measure group para suas dimensões

measures

Cada entrada é uma definição de medida (tem name) ou uma referência de medida (tem ref, usada em measure groups agregados).

Definição de medida:

ChaveObrigatórioNotas
namesimNome de exibição da medida
columnnãoColuna fonte
aggregatorsimsum, count, distinct-count, min, max, avg
format_stringnãoFormat string MDX (ex.: "#,###.00", "Standard", "Currency")
datatypenãoNumeric, Integer, String
propertiesnãoLista de mapas {name, value} ou {name, expression}
annotationsnãoMapa de nome de annotation → texto

Referência de medida (apenas measure groups agregados):

ChaveObrigatórioNotas
refsimNome da medida referenciada do grupo primário
agg_columnnãoColuna na tabela agregada segurando o valor pré-agregado
measures:
- name: "Unit Sales"
column: "unit_sales"
aggregator: "sum"
format_string: "Standard"
- name: "Customer Count"
column: "customer_id"
aggregator: "distinct-count"
format_string: "#,###"
# Inside an aggregate measure group:
- ref: "Unit Sales"
agg_column: "unit_sales_sum"
- ref: "Fact Count"
agg_column: "fact_count"

Cada link tem um campo type que determina o tipo de join.

foreign_key — o join padrão da tabela de fato para a dimensão via uma coluna FK:

ChaveObrigatórioNotas
typesim"foreign_key"
dimensionsimNome da dimensão
foreign_key_columnnão*Nome da coluna FK única
foreign_keynão*Lista de nomes de coluna FK (FK composta)
attributenãoAtributo ao qual juntar quando a FK não aponta para a chave da dimensão

copy — a tabela agregada herda dados de dimensão de outro measure group:

ChaveObrigatórioNotas
typesim"copy"
dimensionsimNome da dimensão
column_refsnãoLista de mapas {table, name, agg_column}

no_link — este measure group não liga a esta dimensão:

ChaveObrigatórioNotas
typesim"no_link"
dimensionsimNome da dimensão

fact — os dados da dimensão vêm diretamente da tabela de fato:

ChaveObrigatórioNotas
typesim"fact"
dimensionsimNome da dimensão

reference — a dimensão é alcançada indiretamente via outro atributo de dimensão:

ChaveObrigatórioNotas
typesim"reference"
dimensionsimNome da dimensão
via_dimensionnãoNome da dimensão intermediária
via_attributenãoAtributo na dimensão intermediária usado para juntar

Exemplo completo de measure group:

measure_groups:
- name: "Sales"
table: "sales_fact_1997"
measures:
- name: "Unit Sales"
column: "unit_sales"
aggregator: "sum"
format_string: "Standard"
- name: "Store Cost"
column: "store_cost"
aggregator: "sum"
format_string: "#,###.00"
- name: "Customer Count"
column: "customer_id"
aggregator: "distinct-count"
format_string: "#,###"
dimension_links:
- type: "foreign_key"
dimension: "Store"
foreign_key_column: "store_id"
- type: "foreign_key"
dimension: "Time"
foreign_key_column: "time_id"
- type: "foreign_key"
dimension: "Product"
foreign_key_column: "product_id"
- table: "agg_c_special_sales_fact_1997"
type: "aggregate"
measures:
- ref: "Fact Count"
agg_column: "fact_count"
- ref: "Unit Sales"
agg_column: "unit_sales_sum"
dimension_links:
- type: "foreign_key"
dimension: "Store"
foreign_key_column: "store_id"
- type: "copy"
dimension: "Time"

calculated_members

ChaveObrigatórioNotas
namesimNome do membro
dimensionnãoDimensão alvo (ex.: "Measures")
hierarchynãoNome único MDX da hierarquia alvo
parentnãoNome único MDX do membro pai
formulanãoFórmula MDX
format_stringnãoFormat string MDX
captionnãoCaption de exibição mostrado em ferramentas cliente
descriptionnãoDescrição legível
visiblenãofalse esconde o membro de ferramentas cliente (default true; omita quando true)
cell_formatternãoCell formatter customizado — {class_name} ou {script: {language, body}}
propertiesnãoLista de mapas {name, value} ou {name, expression}
annotationsnãoMapa de nome de annotation → texto
calculated_members:
- name: "Profit"
dimension: "Measures"
formula: "[Measures].[Store Sales] - [Measures].[Store Cost]"
properties:
- name: "FORMAT_STRING"
value: "$#,##0.00"
- name: "Profit Growth"
dimension: "Measures"
formula: >-
([Measures].[Profit] - [Measures].[Profit last Period])
/ [Measures].[Profit last Period]
properties:
- name: "FORMAT_STRING"
value: "0.0%"

named_sets

named_sets:
- name: "Top Sellers"
formula: "TopCount([Warehouse].[Warehouse Name].MEMBERS, 5, [Measures].[Warehouse Sales])"

roles

Uma lista de definições de role.

ChaveObrigatórioNotas
namesimNome da role
class_namenãoClasse Java implementando lógica customizada de role
schema_grantnãoGrant de nível superior

schema_grant tem access ("all", "none", "all_dimensions", "custom") e uma lista cubes opcional. Cada grant de cubo pode conter listas dimensions e hierarchies com seus próprios grants. Hierarchy grants suportam top_level, bottom_level, rollup_policy ("full", "partial", "hidden") e uma lista members.

roles:
- name: "California manager"
schema_grant:
access: "none"
cubes:
- cube: "Sales"
access: "all"
dimensions:
- dimension: "Gender"
access: "none"
hierarchies:
- hierarchy: "[Store].[Stores]"
access: "custom"
top_level: "[Store].[Stores].[Store Country]"
members:
- member: "[Store].[Stores].[USA].[CA]"
access: "all"
- member: "[Store].[Stores].[USA].[CA].[Los Angeles]"
access: "none"

XML vs YAML lado a lado

Aqui está a mesma dimensão Time em ambos os formatos:

shared_dimensions:
Time:
table: "time_by_day"
key: "Time Id"
type: "TIME"
attributes:
- name: "Year"
key_column: "the_year"
level_type: "TimeYears"
has_hierarchy: false
- name: "Quarter"
key:
- "the_year"
- "quarter"
name_column: "quarter"
level_type: "TimeQuarters"
has_hierarchy: false
- name: "Month"
key:
- "the_year"
- "month_of_year"
name_column: "the_month"
level_type: "TimeMonths"
has_hierarchy: false
- name: "Time Id"
key_column: "time_id"
has_hierarchy: false
hierarchies:
- name: "Time"
has_all: false
levels:
- "Year"
- "Quarter"
- "Month"

Codificação de referência de coluna

Duas convenções de codificação aparecem ao longo do formato YAML:

Referências qualificadas table.column — dentro de listas key e name_columns, uma coluna pertencente a uma tabela não default é escrita como "table_name.column_name". O conversor divide no primeiro . para recuperar tabela e coluna:

key:
- "product_class.product_family"
- "product_class.product_department"
- "brand_name" # unqualified — belongs to the default table

Tokens {col:...} — dentro de bodies de expressão SQL para colunas calculadas, referências de coluna inline usam {col:column_name} ou {col:table.column_name}. O conversor as parseia de volta para elementos <Column>:

expression:
mysql: "CONCAT({col:fname}, ' ', {col:lname})"
generic: "{col:fullname}"

Limitações conhecidas

  1. Identificadores contendo um ponto literal — a codificação table.column divide no primeiro caractere .. Nomes de tabela ou coluna que contêm um . (ex.: identificadores quotados) não vão fazer round-trip corretamente. O mesmo se aplica a tokens {col:table.column}.

  2. Espaço em branco em SQL de coluna calculada — o serializador YAML pode introduzir espaço em branco inicial extra em bodies SQL quando um arquivo foi gerado por máquina e lido de novo. SQL com espaço em branco inicial significativo pode adquirir indentação extra em um segundo round-trip.

  3. $ref exige uma URL de arquivo — a resolução de include $ref só funciona quando o schema é carregado via uma propriedade de connect-string Catalog=file:///.... Schemas carregados via CatalogContent não têm diretório base e a resolução $ref é pulada.


Relacionado

  • CLI — converta entre XML e YAML e faça lint de schemas pela linha de comando.
  • Schema designer — gere um schema XML a partir de uma descrição em linguagem natural.
  • Schemas — gerencie e edite schemas salvos no dashboard do Saiku.