W przypadku używania w Oracle Analytics wielowymiarowej bazy danych jako źródła danych mogą wystąpić problemy z wydajnością skutkujące generowaniem nieoptymalnych zapytań z wyrażeniami wielowymiarowymi (MDX).
Modyfikując projekt, można poprawić zapytania MDX generowane przez Oracle Analytics. Może to mieć ogromny wpływ nie tylko na wydajność raportów, ale również na ilość zasobów używanych w bazie danych. Sposób korzystania z obsługiwanych lub nieobsługiwanych funkcji znacząco wpływa na generowane zapytania MDX, a tym samym na wydajność.
Ponieważ każdy przypadek użycia jest niepowtarzalny, zespół programistów powinien przejrzeć opcje, przeanalizować dzienniki zapytań Oracle Analytics i wybrać najlepsze rozwiązanie dla danego przypadku.
Ten temat nie obejmuje problemów z wydajnością wynikających z używanej infrastruktury, czyli np. sieci, przeglądarek lub sposobów prezentowania raportów.
Metodologia
Oracle w celu zwiększenia wydajności zaleca wykonanie wymienionych poniżej działań. Ważne jest, aby rozumieć strukturę zapytań MDX, a także dzienniki zapytań generowane przez Oracle Analytics.
Optymalizacja etapów wyboru
Zoptymalizowanie etapów wyboru pozwala uprościć zapytania MDX, ograniczyć liczbę generowanych zapytań MDX i poprawić wydajność.
Na poniższym rysunku pokazano przykładowe porównanie zoptymalizowanych i niezoptymalizowanych etapów wyboru.
Instrukcje CASE
Instrukcje CASE
nie są obsługiwane w zapytaniach MDX i muszą być zawsze stosowane w Oracle Analytics. Wyjaśniona w tej sekcji logika dotycząca instrukcji CASE
jest również prawdziwa dla większości funkcji, które nie są obsługiwane w zapytaniach MDX (takich jak if null
itp.).
Używanie instrukcji CASE
niesie ze sobą zarówno korzyści, jak i ograniczenia. Gdy instrukcje CASE
są zawarte w formułach raportów, instrukcje te nie są uwzględniane w zapytaniu MDX. Może to uprościć zapytanie MDX i poprawić wydajność wykonywania. Wiąże się to jednak z ograniczeniem skuteczności filtrowania, co oznacza, że zapytanie może zwrócić więcej rekordów niż jest to potrzebne.
Korzystanie z funkcjonalności instrukcji CASE
wiąże się z następującymi ograniczeniami:
CASE
nie łączy wielu elementów, użyta w tej instrukcji kolumna podstawowa powinna być uwzględniona w zapytaniu i widokach jako oddzielna ukryta kolumna.CASE
łączy wiele elementów, nie można uwzględnić kolumny podstawowej w widoku, nie wpływając przy tym na poziom agregacji. W takim przypadku:
SUM
, MAX
, MIN
). Działa to tylko wtedy, gdy wewnętrzna reguła agregacji jest używana do łączenia elementów i dostarcza prawidłowe wyniki.Funkcja FILTER
W przeciwieństwie do instrukcji CASE
funkcja FILTER
może być dostarczana do bazy danych w celu wykonania.
Główną korzyścią płynącą z używania funkcji FILTER
w formułach raportów jest to, że wybór jest stosowany w zapytaniu MDX, a objętość danych obliczanych i pobieranych z bazy danych zostaje ograniczona.
Główną wadą używania funkcji FILTER
jest to, że może ona spowodować zwiększenie liczby wykonywanych zapytań MDX. Domyślnie dla każdej użytej funkcji FILTER
jest wykonywane jedno zapytanie.
Przykładowe porównanie użycia instrukcji CASE i funkcji FILTER
W tym przykładzie użytkownik wnioskuje o raport przedstawiający zysk według kwartału i wybranego numeru SKU produktu. Dodatkowo numery SKU są pogrupowane w 12 kategorii. Do kategorii "Other Cola" są przypisane następujące produkty LOB: "Cola", "Diet Cola" i "Shared Diet Cola".
Zapytanie logiczne instrukcji CASE
jest następujące:
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
Nie występuje grupowanie na podstawie instrukcji CASE
. Generowane jest proste zapytanie MDX, przy czym instrukcja CASE
jest przetwarzana przez 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]
Instrukcja CASE
jest wykonywana na serwerze BI i jest to odzwierciedlone w ustawieniu database 0:0,0
bazy danych:
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]
Alternatywnie można użyć filtra w odniesieniu do miary zysku, aby pobierać tylko wymagane elementy LOB. W tym scenariuszu tworzone są trzy miary z zastosowanymi odpowiadającymi im filtrami.
Zapytanie logiczne instrukcji FILTER
jest następujące:
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
W tym scenariuszu generowane są trzy zapytania, po jednym dla każdego filtra, i występują problemy z wydajnością.
Zapytanie 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] ]]
Zapytanie 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] ]]
Zapytanie 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]
Przykład zastosowanego filtra produktu
Lepszym podejściem jest uwzględnienie w raporcie kolumny produktu z pojedynczą kolumną miary bez filtra. Następnie należy utworzyć filtr uwzględniający wymagane produkty. Aby pogrupować produkty w różne kategorie, należy użyć instrukcji CASE
. W tym scenariuszu jest generowane pojedyncze zapytanie MDX z odfiltrowanymi wierszami i nawet mimo tego, że w Oracle Analytics zastosowano instrukcję CASE
, zapytanie używa podzbioru danych, a nie wszystkich rekordów.
Oto inny scenariusz, w którym instrukcje CASE
powodują problemy z wydajnością.
Programista stosuje instrukcję CASE
w celu zmiany nazw marek, a filtr monitujący pulpitu informacyjnego umożliwia użytkownikom wybór marki.
Ponieważ instrukcja CASE
nie jest obsługiwana w MDX, nie można zastosować filtra Brand2
w zapytaniu MDX. Wybierane są wszystkie marki i nie jest to podejście zoptymalizowane.
W scenariuszach tego typu Oracle zaleca usunięcie instrukcji CASE
i zmianę nazw elementów w bazie danych lub utworzenie aliasów.