Quando si utilizza un database multidimensionale come origine dati in Oracle Analytics, potrebbero verificarsi problemi di prestazioni, con conseguente generazione di query di espressioni multidimensionali (MDX) non ottimali.
Se si modifica la progettazione, è possibile migliorare le query MDX generate da Oracle Analytics. Oltre che sulle prestazioni dei report, questa operazione può influire notevolmente anche sul volume delle risorse utilizzate nel database. Il modo in cui si utilizzano le funzioni supportate o non supportate influisce notevolmente sulle query MDX generate e, di conseguenza, sulle prestazioni.
Poiché ogni caso d'uso è unico, il team di sviluppo deve rivedere le opzioni, analizzare i log delle query di Oracle Analytics e selezionare la soluzione più adatta per ciascun caso d'uso.
In questo argomento non vengono affrontati i problemi di prestazioni causati dall'infrastruttura, come reti, browser o visualizzazione dei report.
Metodologia
Oracle consiglia di effettuare i task riportati di seguito per migliorare le prestazioni. È importante comprendere la struttura delle query MDX nonché i log delle query generati da Oracle Analytics.
Ottimizzazione dei passi di selezione
Quando si ottimizzano i passi di selezione, è possibile semplificare le query MDX, ridurre il numero di query MDX generate e migliorare le prestazioni.
Nella figura riportata di seguito viene mostrato un esempio di confronto tra i passi di selezione ottimizzati e quelli non ottimizzati.
.jpg
Istruzioni CASE
La funzionalità delle istruzioni CASE
non è supportata nelle query MDX e deve sempre essere applicata in Oracle Analytics. La logica illustrata in questa sezione in merito alle istruzioni CASE
è valida per la maggior parte delle funzioni non supportate nelle query MDX (if null
e così via).
L'utilizzo delle istruzioni CASE
comporta vantaggi e svantaggi. Quando si includono le istruzioni CASE
nelle formule dei report, queste non vengono inserite nella query MDX. In questo modo è possibile semplificare la query MDX e migliorare le prestazioni. Tuttavia, il lato negativo è che l'applicazione dei filtri risulta meno efficace, ossia la query potrebbe restituire più record di quanto necessario.
L'uso della funzionalità dell'istruzione CASE
comporta le limitazioni riportate di seguito.
CASE
non combina più membri, la colonna di base utilizzata nell'istruzione deve essere inclusa nella query e nelle viste come colonna separata nascosta.CASE
combina più membri, la colonna di base non può essere inclusa nella vista senza influire sul livello di aggregazione. In questo caso:
SUM
, MAX
, MIN
). Questa soluzione funziona solo se la regola di aggregazione interna viene utilizzata per combinare i membri e fornisce risultati corretti.Funzione FILTER
A differenza della funzionalità dell'istruzione CASE
, la funzione FILTER
può essere inviata al database per l'esecuzione.
Il principale vantaggio dell'uso della funzione FILTER
nelle formule dei report è che la selezione viene applicata nella query MDX e il volume di dati calcolati e recuperati dal database viene ridotto.
Il principale svantaggio dell'uso della funzione FILTER
è che può aumentare il numero di query MDX eseguite. Per impostazione predefinita, viene eseguita una query per ciascuna funzione FILTER
utilizzata.
Esempio delle funzioni CASE e FILTER
In questo esempio, un utente richiede un report che mostra i profitti in base al trimestre e all'unità mantenimento scorte (SKU) del prodotto selezionato. Le unità SKU, inoltre, sono raggruppate in 12 categorie. Alla categoria Other Cola sono assegnati i seguenti prodotti del settore di attività: Cola, Diet Cola e Shared Diet Cola.
.jpg
Di seguito è riportata la query logica dell'istruzione 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
Non sono presenti raggruppamenti basati sull'istruzione CASE
. Viene generata una query MDX semplice con l'istruzione CASE
elaborata da 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]
L'istruzione CASE
viene eseguita su BI Server come indica l'impostazione del database 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]
In alternativa, è possibile utilizzare un filtro applicato alla metrica dei profitti per recuperare solo i membri del settore di attività desiderati. In questo scenario vengono create tre metriche con i filtri corrispondenti applicati.
Di seguito è riportata la query logica dell'istruzione 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
In questo scenario vengono generate tre query, una per ciascun filtro, e si verificano problemi di prestazioni.
Query 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] ]]
Query 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] ]]
Query 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]
Esempio di applicazione del filtro per i prodotti
Un approccio migliore consiste nell'includere la colonna dei prodotti nel report, con un'unica colonna di misura senza alcun filtro. Creare quindi un filtro che includa i prodotti desiderati. Se si desidera raggruppare i prodotti in categorie diverse, utilizzare un'istruzione CASE
. In questo scenario viene generata un'unica query MDX con le righe filtrate e, sebbene Oracle Analytics applichi l'istruzione CASE
, viene utilizzato il subset di dati e non vengono utilizzati tutti i record.
Di seguito è riportato un altro scenario in cui le istruzioni CASE
causano problemi di prestazioni.
Uno sviluppatore applica un'istruzione CASE
per rinominare i marchi e un prompt del dashboard consente agli utenti di selezionare il marchio.
.jpg
.jpg
Poiché l'istruzione CASE
non è supportata nella query MDX, il filtro basato su Brand2
non può essere applicato nella query MDX. Tutti i marchi vengono selezionati, per un risultato non ottimizzato.
.jpg
In questo tipo di scenario Oracle consiglia di rimuovere l'istruzione CASE
e rinominare i membri nel database o creare alias.