Ao usar um banco de dados multidimensional como origem de dados no Oracle Analytics, você pode ter problemas de desempenho que resultam na geração de consultas MDX (multidimensional expression) não ideais.
Modificando o design, você pode melhorar as consultas MDX geradas pelo Oracle Analytics. Isso pode ter um grande impacto, não apenas no desempenho do seu relatório, mas também no volume de recursos usados no banco de dados. A maneira como você usa funções com ou sem suporte tem grande impacto nas consultas MDX geradas e, portanto, no desempenho.
Como cada caso de uso é único, sua equipe de desenvolvimento deve verificar as opções, analisar os logs de consulta do Oracle Analytics e selecionar a melhor solução para seu caso de uso.
Este tópico não trata de problemas de desempenho causadas por sua infraestrutura, como redes, browsers ou apresentação de relatório.
Metodologia
A Oracle recomenda que você execute as seguintes tarefas para aumentar o desempenho. É importante que você entenda a estrutura da consulta MDX, bem como os logs de consulta gerados pelo Oracle Analytics.
Otimização de Etapas de Seleção
Ao otimizar etapas de seleção, você pode simplificar as consultas MDX, reduzir o número de consultas MDX geradas e melhorar o desempenho.
A figura a seguir mostra um exemplo de comparação de etapas de seleção otimizadas e não otimizadas.
.jpg
Instruções CASE
Não há suporte para a funcionalidade da instrução CASE
em consultas MDX e ela deve sempre ser aplicada no Oracle Analytics. A lógica explicada nesta seção com relação às instruções CASE
é válida para a maioria das funções para as quais não há suporte na MDX (if null
etc.).
Há vantagens e desvantagens ao usar instruções CASE
. Quando você inclui instruções CASE
em fórmulas de relatório, elas não são incluídas na consulta MDX. Essa opção pode simplificar a consulta MDX e melhorar o desempenho. Porém, a compensação é que você não pode filtrar com a mesma eficácia, o que significa que a consulta pode retornar mais registros do que o necessário.
Veja a seguir as restrições de uso da funcionalidade de instrução CASE
:
CASE
não combinar vários membros, a coluna de base usada na instrução deverá ser incluída na consulta e nas views como uma coluna separada oculta.CASE
combinar vários membros, a coluna de base não poderá ser incluída na view sem impactar o nível de agregação. Se este for o caso:
SUM
, MAX
, MIN
). Isso só vai funcionar se a regra de agregação interna for usada para combinar membros, e produzirá resultados corretos.Função FILTER
Diferentemente da funcionalidade de instrução CASE
, a função FILTER
pode ser enviada ao banco de dados para execução.
O principal benefício de usar a função FILTER
nas fórmulas de relatório é que a seleção é aplicada na consulta MDX e o volume de dados calculados e recuperados do banco de dados é reduzido.
A principal desvantagem de usar a função FILTER
é que ela pode aumentar o número de consultas MDX executadas. Por padrão, uma consulta é executada para cada função FILTER
usada.
Exemplo de CASE versus FILTER
Neste exemplo, um usuário solicita um relatório que mostra o lucro por trimestre e a SKU do produto selecionado. Além disso, as SKUs são agrupadas em 12 categorias. A categoria Outros Refrigerantes Cola tem os seguintes produtos LOB designados: Cola, Diet Cola e Shared Diet Cola.
.jpg
Eis aqui 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 há agrupamento baseado na instrução CASE
. Uma consulta MDX simples é gerada, 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 isso é visto pela definição do banco de dados configurada 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]
Alternativamente, você pode usar um filtro com base na métrica de lucro para recuperar apenas os membros LOB necessários. Neste cenário, você cria três métricas com os filtros correspondentes aplicados.
Eis aqui 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 você tem 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 você quiser agrupar os produtos em categorias diferentes, use uma instrução CASE
. Neste cenário, uma única consulta MDX é gerada com as linhas filtradas e, embora a instrução CASE
seja aplicada pelo Oracle Analytics, ela usa o subconjunto de dados e não todos os registros.
Este é outro cenário em que as instruções CASE
causam problemas de desempenho.
Um desenvolvedor aplica uma instrução CASE
para renomear marcas e um prompt do painel de controle permite que os usuários selecionem a marca.
.jpg
.jpg
Como não há suporte para a instrução CASE
no MDX, o filtro em Brand2
não pode ser aplicado na consulta MDX. Todas as marcas são selecionadas, e isso não é otimizado.
.jpg
Nesse tipo de cenário, a Oracle recomenda que você remova a instrução CASE
e renomeie os membros no banco de dados ou crie aliases.