Atunci când utilizaţi o bază de date multidimensională ca sursă de date în Oracle Analytics, puteţi întâmpina probleme de performanţă, în urma cărora sunt generate interogări de expresie multidimensională (MDX) suboptime.
Prin modificarea designului, puteţi îmbunătăţi interogările MDX pe care le generează Oracle Analytics. Acest lucru poate avea un impact semnificativ nu doar asupra performanţei rapoartelor dvs., ci şi asupra volumului de resurse utilizate în baza de date. Modul în care utilizaţi funcţiile acceptate sau neacceptate are un impact semnificativ asupra interogărilor MDX generate şi, prin urmare, asupra performanţei.
Având în vedere că fiecare caz de utilizare este unic, echipa de dezvoltare ar trebui să examineze opţiunile, să analizeze jurnalele de interogări Oracle Analytics şi să selecteze cea mai bună soluţie pentru cazul dvs. de utilizare.
Acest subiect nu se referă la problemele de performanţă cauzate de infrastructura dvs., cum ar fi problemele de reţea, browser sau prezentare a raportului.
Metodologie
Oracle recomandă să efectuaţi următoarele sarcini pentru a îmbunătăţi performanţa. Este important să înţelegeţi că structura de interogări MDX, precum şi jurnalele de interogări pe care le generează Oracle Analytics.
Optimizarea paşilor de selecţie
Atunci când optimizaţi paşii de selecţie, puteţi simplifica interogările MDX, reduce numărul de interogări MDX generate şi îmbunătăţi performanţa.
Următoarea figură prezintă un exemplu de comparaţie a paşilor de selecţie optimizaţi şi neoptimizaţi.
.jpg
Instrucţiuni CASE
Funcţionalitatea instrucţiunii CASE
nu este acceptată în interogările MDX şi trebuie aplicată întotdeauna în Oracle Analytics. Logica explicată în această secţiune cu privire la instrucţiunile CASE
este valabilă pentru majoritatea funcţiilor care nu sunt acceptate în interogările MDX (if null
etc.).
Există avantaje şi dezavantaje ale utilizării instrucţiunilor CASE
. Atunci când includeţi instrucţiuni CASE
în formulele raportului, acestea nu sunt incluse în interogarea MDX. Acest lucru poate simplifica interogarea MDX şi poate îmbunătăţi performanţa. Cu toate acestea, dezavantajul este că nu puteţi filtra la fel de eficient, ceea ce înseamnă că interogarea ar putea returna mai multe înregistrări decât este necesar.
Următoarele sunt restricţii pentru utilizarea funcţionalităţii instrucţiunii CASE
:
CASE
nu combină mai mulţi membri, coloana de bază utilizată în instrucţiune ar trebui inclusă în interogare şi vizualizări ca o coloană ascunsă separată.CASE
combină mai mulţi membri, coloana de bază nu poate fi inclusă în vizualizare fără a afecta nivelul de agregare. În acest caz:
SUM
, MAX
, MIN
). Acest lucru este posibil numai dacă regula de agregare internă este utilizată pentru a combina membri şi produce rezultate corecte.Funcţia FILTER
Spre deosebire de funcţionalitatea instrucţiunii CASE
, funcţia FILTER
poate fi expediată către baza de date pentru execuţie.
Principalul avantaj al utilizării funcţiei FILTER
în formulele raportului este că selecţia se aplică în interogarea MDX şi volumul de date calculate şi preluate din baza de date este redus.
Principalul dezavantaj al utilizării funcţiei FILTER
este că poate mări numărul de interogări MDX executate. În mod prestabilit, se execută o singură interogare pentru fiecare funcţie FILTER
utilizată.
Exemplu CASE vs. FILTER
În acest exemplu, un utilizator solicită un raport care prezintă profitul în funcţie de semestru şi codul unic de stocare al produsului selectat. În plus, codurile unice de stocare sunt grupate împreună în 12 categorii. Categoria "Alte tipuri de Cola" are următoarele produse din domeniul de activitate asignate: Cola, Diet Cola şi Shared Diet Cola.
.jpg
Iată interogarea logică pentru instrucţiunea 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
Nu există grupare pe baza instrucţiunii CASE
. Este generată o interogare MDX simplă, cu instrucţiunea CASE
procesată de 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]
Instrucţiunea CASE
este executată pe serverul BI şi este văzută de setarea bazei de date setată la 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]
Alternativ, puteţi utiliza un filtru pentru subindicatorul de profit, pentru a prelua doar membrii necesari din domeniul de activitate. În acest scenariu, creaţi trei metrici cu filtrele corespunzătoare aplicate.
Iată interogarea logică pentru instrucţiunea 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
În acest scenariu, sunt generate trei interogări, câte una pentru fiecare filtru, şi puteţi întâmpina probleme de performanţă.
Interogarea 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] ]]
Interogarea 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] ]]
Interogarea 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]
Exemplu de filtru aplicat pentru produs
O abordare mai eficientă presupune includerea în raport a coloanei de produse, cu o singură coloană de subindicatori, fără filtru. Apoi, creaţi un filtru care include produsele necesare. Dacă doriţi să grupaţi produsele în categorii diferite, utilizaţi o instrucţiune CASE
. În acest scenariu, se generează o singură interogare MDX cu rândurile filtrate şi chiar dacă este aplicată instrucţiunea CASE
de către Oracle Analytics, utilizează subsetul de date, nu toate înregistrările.
Iată un alt scenariu, în care instrucţiunea CASE
provoacă probleme de performanţă.
Un dezvoltator aplică o instrucţiune CASE
pentru a redenumi mărcile, iar un prompt de dashboard permite utilizatorilor să selecteze marca.
.jpg
.jpg
Deoarece instrucţiunea CASE
nu este acceptată în MDX, filtrul pentru Brand2
nu poate fi aplicat în interogarea MDX. Toate mărcile sunt selectate, fără optimizare.
.jpg
În acest tip de scenariu, Oracle vă recomandă să eliminaţi instrucţiunea CASE
şi să redenumiţi membrii din baza de date sau să creaţi aliasuri.