Blog SAPienteSAPiente
CDS Views

CDS Analíticas no SAP: como montar dimension, cube e analytical query do jeito certo

SAPiente9 de jun. de 2026· 5 min read

Por que uma query analítica não é um SELECT?

O maior mal-entendido sobre CDS analíticas é tratá-las como uma view comum. Não são. Um modelo analítico no S/4HANA é interpretado pelo Analytic Manager (a engine OLAP do ABAP) — ele lê as anotações, monta o plano, agrega, aplica exception aggregation e hierarquias, e devolve o resultado para o front-end (Analysis for Office, SAC, Query Browser). Rodar SELECT FROM zquery em ABAP simplesmente não invoca essa engine.

Por isso a modelagem analítica tem regras próprias: você encadeia views com papéis bem definidos — dimension, text, hierarchy, fact, cube e query — num esquema estrela. Errar a anotação que define o papel, ou esquecer a semântica de moeda, gera números errados silenciosamente.

Fluxo de chamada de uma CDS Analítica.

Este guia mostra como montar cada componente do jeito certo: o modelo multidimensional, as anotações que definem cada view, measures e agregação, a query moderna via projection view, autorização e os anti-patterns que mais aparecem.

O essencial em 4 linhas

  • Um modelo analítico é interpretado pelo Analytic Manager, não executado como SQL puro.

  • O papel de cada view vem das anotações: @Analytics.dataCategory (#CUBE/#FACT/#DIMENSION) e @ObjectModel.dataCategory (#TEXT/#HIERARCHY).

  • No cube: todo campo com @DefaultAggregation vira measure; o resto vira dimensão. A autorização (DCL) mora aqui.

  • A query moderna (ABAP Cloud) é uma projection view transient com provider contract analytical_query.

Como funciona um modelo analítico CDS?

O modelo segue o schema estrela/floco de neve: fatos no centro, dimensões ao redor. Cada peça tem uma função específica, e elas se encadeiam até a query consumida pelo usuário. Diferente de um SELECT, a query analítica não é SQL — é um artefato que o Analytic Engine interpreta.

Componente

Papel

Fact

Valores transacionais mensuráveis (quantidades, valores).

Dimension

Atributos descritivos (cliente, material, centro de custo).

Text / Hierarchy

Descrições por idioma e estruturas pai-filho ligadas à dimensão.

Cube

Onde fatos + dimensões se conectam — o "InfoProvider".

Query

Projeção do cubo para um cenário de consumo do usuário final.

Quais anotações definem o papel de cada view?

Aqui mora um erro comum: misturar os dois namespaces de dataCategory. São duas anotações distintas, cada uma com seu conjunto de valores. Confundi-las gera erro de ativação ou um papel errado. O mapeamento correto, conforme a ABAP Keyword Documentation:

Categoria

Anotação → Valor

Cube / Fact

@Analytics.dataCategory: #CUBE / #FACT

Dimension

@Analytics.dataCategory: #DIMENSION

Aggregation level

@Analytics.dataCategory: #AGGREGATIONLEVEL

Text

@ObjectModel.dataCategory: #TEXT

Hierarchy

@ObjectModel.dataCategory: #HIERARCHY

Value help

@ObjectModel.dataCategory: #VALUE_HELP

Correção que vale ouro: #TEXT e #HIERARCHY pertencem a @ObjectModel.dataCategory, não a @Analytics.dataCategory. E não existe @ObjectModel.dataCategory: #MASTER_DATA — "master data" se expressa com @Analytics.dataCategory: #DIMENSION + @ObjectModel.representativeKey, não com um literal de categoria.

Como modelar uma Dimension View?

A dimension view representa master data — entidades descritivas (cliente, material, centro de custo) que classificam e filtram os fatos. É o ponto de entrada para textos, hierarquias e atributos navegáveis a partir do cubo. Use-a quando vários cubos precisam navegar para os mesmos atributos.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Dimensão - Cliente'
@Analytics.dataCategory: #DIMENSION
@VDM.viewType: #BASIC
@ObjectModel.representativeKey: 'Customer'
define view entity ZI_Customer
  as select from kna1
  association [0..*] to ZI_CustomerText as _Text
                       on $projection.Customer = _Text.Customer
{
  @ObjectModel.text.association: '_Text'
  key kunnr  as Customer,

  @ObjectModel.foreignKey.association: '_Country'
      land1  as Country,

      name1  as CustomerName,
      _Text
}

Três anotações garantem a integridade: @ObjectModel.representativeKey define a chave de negócio (obrigatória para os cubos fazerem foreign-key association); @ObjectModel.text.association liga a view de textos (sem ela, o front mostra só a chave técnica); e @ObjectModel.foreignKey.association em cada atributo garante navegação consistente.

A Text View (@ObjectModel.dataCategory: #TEXT) fornece descrições por idioma — o campo de idioma precisa de @Semantics.language: true e o descritivo de @Semantics.text: true. A Hierarchy View (@ObjectModel.dataCategory: #HIERARCHY) modela relações pai-filho via @Hierarchy.parentChild.recurse, sempre com associação de volta para a dimensão.

Como modelar o Cube View?

O cube é o coração do modelo: consolida fatos e associações para dimensões num star schema, e é o único tipo de view sobre o qual queries analíticas podem ser construídas. A classificação de cada campo é automática — todo campo com @DefaultAggregation vira measure; sem ela, vira dimensão.

@AccessControl.authorizationCheck: #MANDATORY
@EndUserText.label: 'Cubo - Vendas'
@Analytics.dataCategory: #CUBE
@VDM.viewType: #COMPOSITE
define view entity ZI_SalesCube
  as select from ZI_SalesOrderItemFact as Fact
  association [0..1] to ZI_Customer as _Customer
                       on $projection.Customer = _Customer.Customer
{
  key Fact.SalesOrder,
  key Fact.SalesOrderItem,

  @ObjectModel.foreignKey.association: '_Customer'
      Fact.Customer,

  @DefaultAggregation: #SUM
  @Semantics.amount.currencyCode: 'Currency'
      Fact.NetAmount,
      Fact.Currency,

      _Customer
}

Três regras inegociáveis no cube: (1) declare @Semantics.amount.currencyCode em todo valor monetário e @Semantics.quantity.unitOfMeasure em quantidades — sem isso o front soma BRL + USD como números puros. (2) Declare @DefaultAggregation em toda measure — esquecer faz o engine tratar o campo como dimensão e duplicar linhas. (3) Use @AccessControl.authorizationCheck: #MANDATORY — a proteção mora no cubo.

E não use parâmetros no cube (exceto conversão de moeda): parâmetros transformam o objeto numa HANA table function e degradam a performance. Mova-os para a query.

Como funcionam as measures e a exception aggregation?

O @DefaultAggregation define como o engine agrega o campo quando o usuário muda o drill-down. Os valores válidos são #SUM, #MIN, #MAX, #AVG, #COUNT, #COUNT_DISTINCT, #FORMULA e #NONE. Há também a anotação mais recente @Aggregation.default — relacionada, mas com conjunto de valores ligeiramente diferente (não são sinônimos exatos).

A exception aggregation permite agregar primeiro por um conjunto de campos diferente do drill-down do usuário — essencial para "contar pedidos distintos" sem inflar por linhas de item. Aqui o guia que originou este post tinha a anotação errada; a forma correta usa o namespace @AnalyticsDetails:

@DefaultAggregation: #SUM
@AnalyticsDetails: { exceptionAggregationSteps: [
   { exceptionAggregationBehavior: #COUNT_DISTINCT,
     exceptionAggregationElements: [ 'SalesOrder' ] } ] }
1 as OrderCount

Correção de sintaxe: exception aggregation é @AnalyticsDetails.exceptionAggregationStepsnão @Aggregation.exception, como circula por aí. Os campos internos (exceptionAggregationBehavior, exceptionAggregationElements) estão corretos. Com isso, mesmo arrastando por Cliente/Material/Mês, o OrderCount conta pedidos distintos, não linhas.

Como expor a query analítica (o jeito moderno)?

No ABAP Cloud / S/4HANA Cloud, a forma atual e recomendada de criar uma query é uma projection view transient com provider contract analytical_query. O transient é obrigatório — significa que não existe view SQL no banco; o objeto só vive em runtime, interpretado pela engine. A fonte projetada deve ser um cube (ou dimension).

@EndUserText.label: 'Query - Análise de Vendas'
@AccessControl.authorizationCheck: #NOT_ALLOWED
@Metadata.allowExtensions: true
@Analytics.query: true
define transient view entity ZC_SalesAnalysisProj
  provider contract analytical_query
  as projection on ZI_SalesCube
{
  @AnalyticsDetails.query.axis: #ROWS
  @AnalyticsDetails.query.display: #KEY_TEXT
  Customer,

  @AnalyticsDetails.query.axis: #FREE
  Material,

  @AnalyticsDetails.query.axis: #COLUMNS
  @AnalyticsDetails.query.decimals: 2
  @Semantics.amount.currencyCode: 'Currency'
  NetAmount,
  Currency
}

Legado vs. atual: a query antiga era uma view entity comum com apenas @Analytics.query: true (modelo V2). Para desenvolvimento novo, prefira a projection view transient (V3) acima. E note a autorização: a query usa @AccessControl.authorizationCheck: #NOT_ALLOWED — ela não é lida por ABAP SQL, então não recebe DCL; herda o filtro do cubo.

As anotações de layout posicionam os campos no grid: @AnalyticsDetails.query.axis aceita #ROWS, #COLUMNS e #FREE; .display aceita #KEY, #TEXT, #TEXT_KEY e #KEY_TEXT; e há .totals, .decimals e .scaling para subtotais e formatação.

Como criar restricted e calculated measures?

Uma restricted measure soma só as linhas que satisfazem uma condição — implementada com CASE na query. Já uma calculated measure com fórmula é o que garante razões e percentuais corretos, porque o cálculo acontece após a agregação:

@DefaultAggregation: #FORMULA
@AnalyticsDetails.query.formula: 'NDIV0( NetAmount / OrderQuantity )'
@AnalyticsDetails.query.decimals: 2
@EndUserText.label: 'Preço Médio Unitário'
1 as AvgUnitPrice

Por que #FORMULA importa: se você calcular NetAmount / OrderQuantity linha a linha e depois somar, o número fica errado. Com #FORMULA, o engine soma primeiro NetAmount e OrderQuantity e só então divide — matematicamente correto. A anotação da fórmula é @AnalyticsDetails.query.formula (com o segmento .query.).

Como adicionar variáveis e filtros de consumo?

A anotação @Consumption.filter cria uma variável de prompt para o usuário no front-end. O selectionType aceita #SINGLE, #INTERVAL, #RANGE e #HIERARCHY_NODE, e o @AnalyticsDetails.query.variableSequence define a ordem dos prompts.

@Consumption.filter: { selectionType: #SINGLE,
                       multipleSelections: false,
                       mandatory: true }
@AnalyticsDetails.query.variableSequence: 10
CompanyCode,

@Consumption.filter: { selectionType: #INTERVAL,
                       multipleSelections: false,
                       mandatory: true }
@AnalyticsDetails.query.variableSequence: 20
FiscalPeriod,

Onde fica a autorização (DCL) num modelo analítico?

A regra é direta: a autorização mora no cube, não na query. O cube usa @AccessControl.authorizationCheck: #MANDATORY + uma DCL; a query usa #NOT_ALLOWED e herda o filtro automaticamente. Aplicar DCL na query e deixar o cubo aberto fura o controle quando o cubo for consumido em outro contexto.

// DCL do cubo: ZI_SalesCube.dcls
@MappingRole: true
define role ZI_SalesCube {
  grant select on ZI_SalesCube
    where Plant = aspect pfcg_auth( Z_SD_PLANT, WERKS, ACTVT = '03' );
}

Quer dominar a camada de DCL? A autorização por linha tem regras próprias (condições PFCG, user, herança). Veja o guia completo de CDS Access Control (DCL). Em ABAP Cloud, prefira aspect user e business roles para Clean Core.

O que muda entre On-Premise e ABAP Cloud?

A diferença central é o Clean Core. On-premise, você acessa tabelas direto e usa a query clássica; em ABAP Cloud, só views released e a projection view com provider contract são o caminho.

Tópico

On-Premise

ABAP Cloud

Acesso a tabelas

Direto (KNA1, VBAP…)

Só views released (C1/C2)

Query

@Analytics.query: true

Projection view + provider contract analytical_query

DCL

pfcg_auth

aspect user, business roles

Ferramentas

RSRT, Query Browser, KPI Modeler

Custom Analytical Queries, SAC, Datasphere

Quais são os anti-patterns mais comuns?

A maioria dos modelos analíticos quebrados sofre dos mesmos enganos. Conhecê-los antes economiza retrabalho — e evita relatórios que somam errado sem ninguém perceber.

Anti-pattern

Como corrigir

Calcular valor / qtd no SELECT e usar #SUM

Use @DefaultAggregation: #FORMULA na query

Esquecer @Semantics.amount.currencyCode

Sempre anote moeda/UM ao lado do valor/qtd

Usar parâmetros no cube

Mova para a query (cube vira table function)

DCL na query, não no cube

DCL sempre no cube; query #NOT_ALLOWED

Esquecer @DefaultAggregation numa measure

Anote toda measure (senão vira dimensão)

Consumir a query via Open SQL

Consuma via OData/Analytic Manager

Perguntas frequentes

Posso consumir uma query analítica com SELECT em ABAP?

Não como esperado. A query é interpretada pelo Analytic Manager, não executada como SQL. Um SELECT FROM zquery não invoca a engine analítica (agregação, exception aggregation, variáveis). Consuma via OData/Analytic Manager — Analysis for Office, SAC, Query Browser. Na V3, a query é até transient, sem view SQL no banco.

Qual a diferença entre measure e dimensão num cube?

A classificação é automática pela anotação: todo campo com @DefaultAggregation é tratado como measure (agregável); sem ela, vira dimensão (atributo de agrupamento). Esquecer o @DefaultAggregation num campo numérico é o erro clássico — o engine o trata como dimensão e o resultado aparece com linhas duplicadas.

Onde devo colocar a autorização: no cube ou na query?

No cube, sempre. Ele usa @AccessControl.authorizationCheck: #MANDATORY + DCL; a query usa #NOT_ALLOWED e herda o filtro. A query não pode ser lida por ABAP SQL, então não recebe DCL própria. Proteger só a query deixa o cubo exposto quando consumido em outro contexto.

Por que minha razão (preço médio) está somando errado?

Porque você provavelmente calculou linha a linha e depois somou. Soma de razões não é razão de somas. Use uma calculated measure com @DefaultAggregation: #FORMULA e @AnalyticsDetails.query.formula: o engine soma os operandos primeiro e só então aplica a fórmula — o resultado fica correto.

Posso usar parâmetros num Cube View?

Evite. Parâmetros num cube o transformam numa HANA table function, com impacto sério de performance — uma prática desaconselhada amplamente. A exceção comum é conversão de moeda. Para filtros e prompts, use a query com @Consumption.filter e variáveis, que é onde eles pertencem.

Conclusão

Modelar CDS analíticas é menos sobre SQL e mais sobre anotações corretas. Acerte o papel de cada view (@Analytics.dataCategory vs. @ObjectModel.dataCategory), proteja a semântica de moeda e quantidade, declare as measures com @DefaultAggregation e deixe a autorização no cube. Para a query, prefira a projection view transient com provider contract — o modelo atual do ABAP Cloud.

E guarde as duas correções que este guia traz: exception aggregation é @AnalyticsDetails.exceptionAggregationSteps, e a query analítica usa #NOT_ALLOWED, não #NOT_REQUIRED. Com as anotações certas, a engine faz o trabalho pesado — e os números chegam certos ao usuário.

Referências oficiais

TagsDesenvolvedorClean CoreHANA
Avalie este conteúdo
para avaliar
SAPiente
@sapiente

Blog sobre SAP, ABAP, BTP, Fiori e tudo que envolve o ecossistema SAP.

Ver perfil →
FacebookInstagramYouTubeTwitter

Comentários 0

Entre na conversa

Faça login para deixar seu comentário neste artigo.

Ainda sem comentários

Seja o primeiro a comentar este artigo.