Ao utilizar uma base de dados multidimensional como origem de dados no Oracle Analytics, poderão ocorrer problemas de desempenho que resultam na geração de consultas de MDX (Multidimensional Expression) não otimizadas.
Ao modificar o design, pode melhorar as consultas de MDX geradas pelo Oracle Analytics. Isto pode ter um enorme impacto, não apenas no desempenho do seu relatório, mas também no volume de recursos utilizados na base de dados. A forma como utiliza as funções suportadas ou não suportadas tem um grande impacto nas consultas de MDX geradas e, por conseguinte, no desempenho.
Como cada caso de utilização é exclusivo, a sua equipa de desenvolvimento deve rever as opções, analisar os diários de consultas do Oracle Analytics e selecionar a melhor solução para o seu caso de utilização.
Este tópico não aborda os problemas de desempenho causados pela sua infraestrutura, como redes, browsers ou apresentação de relatórios.
Metodologia
A Oracle recomenda que efetue as seguintes tarefas para aumentar o desempenho. É importante que compreenda a estrutura da consulta de MDX, bem como os diários de consultas que o Oracle Analytics gera.
Otimização dos Passos de Seleção
Ao otimizar os passos de seleção, pode simplificar as consultas de MDX, reduzir o número de consultas de MDX geradas e aumentar o desempenho.
A figura seguinte mostra um exemplo de uma comparação de passos de seleção otimizados e não otimizados.
.jpg
Instruções CASE
A funcionalidade da instrução CASE
não é suportada nas consultas de MDX e deve ser sempre aplicada no Oracle Analytics. A lógica explicada nesta secção em relação às instruções CASE
é válida para a maioria das funções que não são suportadas nas consultas de MDX (if null
, etc.).
Existem vantagens e desvantagens na utilização de instruções CASE
. Quando inclui instruções CASE
em fórmulas de relatório, elas não são incluídas na consulta de MDX. Isto pode simplificar a consulta de MDX e melhorar o desempenho. No entanto, em contrapartida não é possível filtrar tão eficazmente, o que significa que a consulta poderá devolver mais registos do que o necessário.
Seguem-se as restrições à utilização da funcionalidade da instrução CASE
:
CASE
não combinar vários membros, a coluna base utilizada na instrução deve ser incluída na consulta e nas visualizações como uma coluna separada oculta.CASE
combinar vários membros, a coluna base não pode ser incluída na visualização sem afetar o nível de agregação. Se for este o caso:
SUM
, MAX
, MIN
). Isto só funciona se a regra de agregação interna for utilizada para combinar membros e fornecer resultados corretos.Função FILTER
Ao contrário da funcionalidade da instrução CASE
, a função FILTER
pode ser enviada para a base de dados para execução.
A principal vantagem de utilizar a função FILTER
em fórmulas de relatório é que a seleção é aplicada na consulta de MDX e o volume de dados calculados e obtidos da base de dados é reduzido.
A principal desvantagem de utilizar a função FILTER
é que pode aumentar o número de consultas de MDX executadas. Por omissão, é executada uma consulta para cada função FILTER
utilizada.
Exemplo de CASE vs. FILTER
Neste exemplo, um utilizador pede um relatório que mostre o lucro por trimestre e a SKU do produto selecionada. Além disso, as SKUs são agrupadas em 12 categorias. A categoria Other Cola tem os seguintes produtos de LOB atribuídos: Cola, Diet Cola e Shared Diet Cola.
.jpg
Segue-se a consulta lógica da instrução CASE
:
SELECT 0 s_0, CASE when XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU" in ('Cola','Diet Cola','Shared Diet Cola') THEN 'Other Cola' ELSE XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU" END s_1, DESCRIPTOR_IDOF(XSA('Admin'.'Sample.BasicPM')."Product"."Category") s_2, DESCRIPTOR_IDOF(XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU") s_3, DESCRIPTOR_IDOF(XSA('Admin'.'Sample.BasicPM')."Year"."Quarter") s_4, SORTKEY(XSA('Admin'.'Sample.BasicPM')."Product"."Category") s_5, SORTKEY(XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU") s_6, SORTKEY(XSA('Admin'.'Sample.BasicPM')."Year"."Quarter") s_7, XSA('Admin'.'Sample.BasicPM')."Product"."Category" s_8, XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU" s_9, XSA('Admin'.'Sample.BasicPM')."Year"."Quarter" s_10, XSA('Admin'.'Sample.BasicPM')."Basic"."Profit" s_11 FROM XSA('Admin'.'Sample.BasicPM') ORDER BY 8 ASC NULLS LAST, 11 ASC NULLS LAST, 5 ASC NULLS LAST, 2 ASC NULLS LAST, 7 ASC NULLS LAST, 10 ASC NULLS LAST, 4 ASC NULLS LAST, 6 ASC NULLS LAST, 9 ASC NULLS LAST, 3 ASC NULLS LAST FETCH FIRST 125001 ROWS ONLY
Não existe nenhum agrupamento baseado na instrução CASE
. É gerada uma consulta de MDX simples, com a instrução CASE
processada pelo Oracle Analytics:
With set [_Product3] as 'Descendants([Product], [Product].Generations(3), leaves)' set [_Year2] as 'Descendants([Year], [Year].Generations(2), leaves)' select { [Measures].[Profit] } on columns, NON EMPTY {crossjoin({[_Year2]},{[_Product3]})} properties GEN_NUMBER, [Product].[MEMBER_UNIQUE_NAME], [Product].[Memnor], [Year].[MEMBER_UNIQUE_NAME], [Year].[Memnor] on rows from [Sample.Basic]
A instrução CASE
é executada no BI Server e isto é visto pela definição da base de dados definida como database 0:0,0
:
RqList <<11777451>> [for database 0:0,0] D1.c6 as c6 [for database 0:0,0], D1.c4 as c4 [for database 0:0,0], case when D1.c7 in ([ 'Cola', 'Diet Cola', 'Shared Diet Cola'] ) then 'Other Cola' else D1.c7 end as c2 [for database 0:0,0], D1.c5 as c5 [for database 0:0,0], D1.c3 as c3 [for database 0:0,0], D1.c1 as c1 [for database 0:0,0], D1.c7 as c7 [for database 0:0,0], D1.c8 as c8 [for database 0:0,0]
Em alternativa, pode utilizar um filtro em relação à métrica de lucro para obter apenas os membros LOB obrigatórios. Neste cenário, são criadas três métricas com os filtros correspondentes aplicados.
Segue-se a consulta lógica da instrução FILTER
:
SELECT 0 s_0, DESCRIPTOR_IDOF(XSA('Admin'.'Sample.BasicPM')."Product"."Category") s_1, DESCRIPTOR_IDOF(XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU") s_2, DESCRIPTOR_IDOF(XSA('Admin'.'Sample.BasicPM')."Year"."Quarter") s_3, SORTKEY(XSA('Admin'.'Sample.BasicPM')."Product"."Category") s_4, SORTKEY(XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU") s_5, SORTKEY(XSA('Admin'.'Sample.BasicPM')."Year"."Quarter") s_6, XSA('Admin'.'Sample.BasicPM')."Product"."Category" s_7, XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU" s_8, XSA('Admin'.'Sample.BasicPM')."Year"."Quarter" s_9, FILTER(XSA('Admin'.'Sample.BasicPM')."Basic"."Profit" USING XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU" in ('Cola','Diet Cola','Shared Diet Cola')) s_10, FILTER(XSA('Admin'.'Sample.BasicPM')."Basic"."Profit" USING XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU" in ('Sasprilla','Birch Beer','Dark Cream')) s_11, FILTER(XSA('Admin'.'Sample.BasicPM')."Basic"."Profit" USING XSA('Admin'.'Sample.BasicPM')."Product"."Product SKU" in ('xxxxx')) s_12 FROM XSA('Admin'.'Sample.BasicPM') ORDER BY 7 ASC NULLS LAST, 10 ASC NULLS LAST, 4 ASC NULLS LAST, 6 ASC NULLS LAST, 9 ASC NULLS LAST, 3 ASC NULLS LAST, 5 ASC NULLS LAST, 8 ASC NULLS LAST, 2 ASC NULLS LAST FETCH FIRST 125001 ROWS ONLY
Neste cenário, são geradas três consultas, uma para cada filtro, e ocorrem problemas de desempenho.
Consulta 1:
With set [_Product3] as 'Filter([Product].Generations(3).members, ((IIF(IsValid([Product].CurrentMember.MEMBER_ALIAS), [Product].CurrentMember.MEMBER_ALIAS, [Product].CurrentMember.MEMBER_Name) = "xxxxx")))' set [_Year2] as 'Descendants([Year], [Year].Generations(2), leaves)' select { [Measures].[Profit] } on columns, NON EMPTY {crossjoin({[_Year2]},{[_Product3]})} properties MEMBER_NAME, GEN_NUMBER, property_expr([Product], [MEMBER_NAME], Ancestor(currentaxismember(), [Product].Generations(2)), "Category_Null_Alias_Replacement"), property_expr([Product], [Default], Ancestor(currentaxismember(), [Product].Generations(2)), "Category"), property_expr([Product], [MEMBER_UNIQUE_NAME], Ancestor(currentaxismember(), [Product].Generations(2)), "Category - Member Key"), property_expr([Product], [Memnor], Ancestor(currentaxismember(), [Product].Generations(2)), "Category - Memnor"), [Product].[MEMBER_UNIQUE_NAME], [Product].[Memnor], [Year].[MEMBER_UNIQUE_NAME], [Year].[Memnor] on rows from [Sample.Basic] ]]
Consulta 2:
With set [_Product3] as 'Filter([Product].Generations(3).members, ((IIF(IsValid([Product].CurrentMember.MEMBER_ALIAS), [Product].CurrentMember.MEMBER_ALIAS, [Product].CurrentMember.MEMBER_Name) = "Birch Beer") OR (IIF(IsValid([Product].CurrentMember.MEMBER_ALIAS), [Product].CurrentMember.MEMBER_ALIAS, [Product].CurrentMember.MEMBER_Name) = "Dark Cream") OR (IIF(IsValid([Product].CurrentMember.MEMBER_ALIAS), [Product].CurrentMember.MEMBER_ALIAS, [Product].CurrentMember.MEMBER_Name) = "Sasprilla")))' set [_Year2] as 'Descendants([Year], [Year].Generations(2), leaves)' select { [Measures].[Profit] } on columns, NON EMPTY {crossjoin({[_Year2]},{[_Product3]})} properties MEMBER_NAME, GEN_NUMBER, property_expr([Product], [MEMBER_NAME], Ancestor(currentaxismember(), [Product].Generations(2)), "Category_Null_Alias_Replacement"), property_expr([Product], [Default], Ancestor(currentaxismember(), [Product].Generations(2)), "Category"), property_expr([Product], [MEMBER_UNIQUE_NAME], Ancestor(currentaxismember(), [Product].Generations(2)), "Category - Member Key"), property_expr([Product], [Memnor], Ancestor(currentaxismember(), [Product].Generations(2)), "Category - Memnor"), [Product].[MEMBER_UNIQUE_NAME], [Product].[Memnor], [Year].[MEMBER_UNIQUE_NAME], [Year].[Memnor] on rows from [Sample.Basic] ]]
Consulta 3:
With set [_Product3] as 'Filter([Product].Generations(3).members, ((IIF(IsValid([Product].CurrentMember.MEMBER_ALIAS), [Product].CurrentMember.MEMBER_ALIAS, [Product].CurrentMember.MEMBER_Name) = "Cola") OR (IIF(IsValid([Product].CurrentMember.MEMBER_ALIAS), [Product].CurrentMember.MEMBER_ALIAS, [Product].CurrentMember.MEMBER_Name) = "Diet Cola") OR (IIF(IsValid([Product].CurrentMember.MEMBER_ALIAS), [Product].CurrentMember.MEMBER_ALIAS, [Product].CurrentMember.MEMBER_Name) = "Shared Diet Cola")))' set [_Year2] as 'Descendants([Year], [Year].Generations(2), leaves)' select { [Measures].[Profit] } on columns, NON EMPTY {crossjoin({[_Year2]},{[_Product3]})} properties MEMBER_NAME, GEN_NUMBER, property_expr([Product], [MEMBER_NAME], Ancestor(currentaxismember(), [Product].Generations(2)), "Category_Null_Alias_Replacement"), property_expr([Product], [Default], Ancestor(currentaxismember(), [Product].Generations(2)), "Category"), property_expr([Product], [MEMBER_UNIQUE_NAME], Ancestor(currentaxismember(), [Product].Generations(2)), "Category - Member Key"), property_expr([Product], [Memnor], Ancestor(currentaxismember(), [Product].Generations(2)), "Category - Memnor"), [Product].[MEMBER_UNIQUE_NAME], [Product].[Memnor], [Year].[MEMBER_UNIQUE_NAME], [Year].[Memnor] on rows from [Sample.Basic]
Exemplo de Filtro de Produto Aplicado
Uma abordagem melhor é incluir a coluna do produto no relatório com uma única coluna de medida sem um filtro. Em seguida, crie um filtro que inclua os produtos necessários. Se pretender agrupar os produtos em diferentes categorias, utilize uma instrução CASE
. Neste cenário, é gerada uma única consulta de MDX com as linhas filtradas e, apesar de a instrução CASE
ser aplicada pelo Oracle Analytics, é utilizado o subconjunto de dados e não todos os registos.
Eis outro cenário em que as instruções CASE
causam problemas de desempenho.
Um programador aplica uma instrução CASE
para renomear marcas e um prompt do dashboard permite que os utilizadores selecionem a marca.
.jpg
.jpg
Como a instrução CASE
não é suportada no MDX, o filtro em Brand2
não pode ser aplicado na consulta de MDX. Todas as marcas são selecionadas, sem qualquer otimização.
.jpg
Neste tipo de cenário, a Oracle recomenda que retire a instrução CASE
e que renomeie os membros na base de dados ou crie pseudónimos.