Performance von multidimensionalen Datenbankabfragen optimieren

Wenn Sie in Oracle Analytics eine multidimensionale Datenbank als Datenquelle verwenden, treten unter Umständen Performanceprobleme auf, aufgrund derer suboptimale Abfragen mit multidimensionalen Ausdrücken (MDX-Abfragen) generiert werden.

Indem Sie das Design ändern, können Sie die von Oracle Analytics generierten MDX-Abfragen verbessern. Das kann enorme Auswirkungen auf die Berichtsperformance und die Menge der belegten Ressourcen in der Datenbank haben. Wie Sie unterstützte oder nicht unterstützte Funktionen verwenden, hat enorme Auswirkungen auf die generierten MDX-Abfragen und damit die Performance.

Weil jeder Anwendungsfall einzigartig ist, muss das Entwicklungsteam die Optionen prüfen, die Oracle Analytics-Abfragelogs analysieren und die beste Lösung für Ihren Anwendungsfall auswählen.

In diesem Thema werden keine Performanceprobleme behandelt, die aufgrund Ihrer Infrastruktur auftreten, wie Netzwerke, Browser oder Berichtspräsentation.

Methode

Oracle empfiehlt, dass Sie die folgenden Maßnahmen ergreifen, um die Performance zu verbessern. Es ist wichtig, dass Sie sowohl die MDX-Abfragestruktur als auch die Abfragelogs verstehen, die Oracle Analytics generiert.

  • Vereinfachen Sie die generierten MDX-Abfragen.
  • Reduzieren Sie die Anzahl der generierten MDX-Abfragen.
  • Stellen Sie sicher, dass optimale Filter und Auswahloptionen in der MDX-Abfrage angewendet wurden.
  • Optimieren Sie die Performance in der multidimensionalen Datenbank zusammen mit dem Datenbankadministrator (DBA), und überprüfen Sie, warum die Quelldatenbank noch eine mangelhafte Performance aufweist.
  • Ändern Sie die Analyse basierend auf dem Feedback des DBA.

Optimierung der Auswahlschritte

Wenn Sie Auswahlschritte optimieren, können Sie die MDX-Abfragen vereinfachen, die Anzahl der generierten MDX-Abfragen reduzieren und die Performance steigern.

Die folgende Abbildung zeigt ein Beispiel für einen Vergleich zwischen optimierten und nicht optimierten Auswahlschritten.

Beschreibung von GUID-43E6F348-B14C-40DC-8C21-DA34DAE44344-default.jpg folgt
.jpg

CASE-Anweisungen

CASE-Anweisungsfunktionalität wird in MDX-Abfragen nicht unterstützt und muss immer in Oracle Analytics angewendet werden. Die in diesem Abschnitt erläuterte Logik in Bezug auf CASE-Anweisungen gilt für die meisten Funktionen, die in MDX-Abfragen nicht unterstützt werden (if null usw.).

Die Verwendung von CASE-Anweisungen hat Vor- und Nachteile. Wenn Sie CASE-Anweisungen in Berichtsformeln einschließen, werden sie nicht in die MDX-Abfrage aufgenommen. Dadurch können Sie die MDX-Abfrage vereinfachen und die Performance verbessern. Das bringt allerdings den Nachteil mit sich, dass die Filterung weniger effektiv wird. Die Abfrage könnte also mehr Datensätze als notwendig zurückgeben.

Bei der Verwendung der CASE-Anweisungsfunktionalität gelten die folgenden Einschränkungen:

  • Wenn die CASE-Anweisung nicht mehrere Elemente kombiniert, muss die in der Anweisung verwendete Basisspalte als ausgeblendete separate Spalte in die Abfrage und die Ansichten aufgenommen werden.
  • Wenn die CASE-Anweisung mehrere Elemente kombiniert, kann die Basisspalte nicht ohne Auswirkungen auf die Aggregationsebene in die Ansicht aufgenommen werden. In diesem Fall gilt Folgendes:
    • Wenn die Aggregationsregel der Kennzahl nicht Externe Aggregation lautet, muss die Basisspalte aus der Abfrage ausgeschlossen werden.
    • Wenn die Aggregationsregel der Kennzahl Externe Aggregation lautet, muss die Basisspalte in die Abfrage aufgenommen und aus der Ansicht ausgeschlossen werden. Sie müssen die Aggregationsregel der Kennzahl vom Standardwert in eine einfache interne Aggregationsregel ändern (SUM, MAX, MIN). Das funktioniert nur, wenn die interne Aggregationsregel zum Kombinieren von Elementen verwendet wird und korrekte Ergebnisse liefert.

FILTER-Funktion

Im Gegensatz zur CASE-Anweisungsfunktion kann die FILTER-Funktion zur Ausführung an die Datenbank übergeben werden.

Der Hauptvorteil der FILTER-Funktion in Berichtsformeln besteht darin, dass die Auswahl in der MDX-Abfrage angewendet wird und die Menge der Daten, die berechnet und von der Datenbank abgerufen werden, reduziert wird.

Der Hauptnachteil der FILTER-Funktion besteht darin, dass sich die Anzahl der ausgeführten MDX-Abfragen dadurch erhöhen könnte. Standardmäßig wird eine Abfrage für jede verwendete FILTER-Funktion ausgeführt.

Beispiel zum Vergleich von CASE und FILTER

In diesem Beispiel fordert ein Benutzer einen Bericht an, der den Gewinn nach Quartal und ausgewählter Produkt-SKU anzeigt. Darüber hinaus sind die SKUs in 12 Kategorien eingeteilt. Der Kategorie "Other Cola" sind die folgenden Geschäftsbereichsprodukte zugewiesen: Cola, Diet Cola und Shared Diet Cola.

Beschreibung von GUID-7198F143-54E6-4A48-9579-96624936A94D-default.jpg folgt
.jpg

Logische Abfrage der CASE-Anweisung:

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

Es gibt keine Gruppierung auf der Basis der CASE-Anweisung. Es wird eine einfache MDX-Abfrage erstellt, und die CASE-Anweisung wird von Oracle Analytics verarbeitet:

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]

Die CASE-Anweisung wird auf dem BI-Server ausgeführt, wie an der Datenbankeinstellung database 0:0,0 zu erkennen ist:

 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 dazu können Sie einen Filter für die Gewinnmetrik verwenden, um nur die erforderlichen Geschäftsbereichselemente abzurufen. In diesem Szenario erstellen Sie drei Metriken mit den entsprechenden angewendeten Filtern.

Logische Abfrage der FILTER-Anweisung:

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 diesem Szenario werden drei Abfragen generiert, eine für jeden Filter, und es treten Performanceprobleme auf.

Abfrage 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]

]]

Abfrage 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]

]]

Abfrage 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]

Beispiel mit angewendetem Produktfilter

Besser wäre es, die Produktspalte mit einer einzelnen Kennzahlenspalte ohne einen Filter in den Bericht aufzunehmen. Erstellen Sie dann einen Filter, der die erforderlichen Produkte enthält. Wenn Sie die Produkte in verschiedene Kategorien einteilen möchten, verwenden Sie eine CASE-Anweisung. In diesem Szenario wird eine einzelne MDX-Abfrage mit den gefilterten Zeilen generiert. Obwohl die CASE-Anweisung von Oracle Analytics angewendet wird, verwendet sie die Teilmenge der Daten und nicht alle Datensätze.

Hier ist ein weiteres Szenario, indem die CASE-Anweisung Performanceprobleme verursacht.

Ein Entwickler wendet eine CASE-Anweisung an, um Marken umzubenennen, und Benutzer können über einen Dashboard-Prompt die Marke auswählen.

Beschreibung von GUID-56356AA9-2AF6-4A67-8ADD-FC4F7F70306C-default.jpg folgt
.jpg

Beschreibung von GUID-E63719C8-9936-412B-8228-F20E8F048C46-default.jpg folgt
.jpg

Da die CASE-Anweisung in der MDX nicht unterstützt wird, kann der Filter für Brand2 nicht in der MDX-Abfrage angewendet werden. Alle Marken werden ausgewählt, was nicht optimal ist.

Beschreibung von GUID-6BE1F274-8257-4E31-8D42-406357A07B2A-default.jpg folgt
.jpg

In diesem Szenario empfiehlt Oracle, die CASE-Anweisung zu entfernen und Elemente in der Datenbank umzubenennen oder Aliasnamen zu erstellen.