Oracle Analytics에서 다차원 데이터베이스를 데이터 소스로 사용하는 경우 최적이 아닌 MDX(다차원 표현식) 질의가 생성되는 성능 문제가 발생할 수 있습니다.
설계를 수정하면 Oracle Analytics가 생성하는 MDX 질의를 향상시킬 수 있습니다. 이는 보고서 성능뿐만 아니라 데이터베이스에서 사용되는 리소스의 양에도 큰 영향을 줄 수 있습니다. 지원되거나 지원되지 않는 함수를 활용하는 방법은 생성된 MDX 질의와 그에 따른 성능에 큰 영향을 끼칩니다.
각 사용 사례는 고유하므로 개발 팀에서 옵션을 검토하고, Oracle Analytics 질의 로그를 분석하고, 사용 사례에 맞는 최적의 솔루션을 선택해야 합니다.
이 항목에서는 네트워크, 브라우저 또는 보고서 표시와 같이 인프라로 인해 발생하는 성능 문제를 다루지 않습니다.
방법론
성능을 향상시키려면 다음 태스크를 완료하는 것이 좋습니다. MDX 질의 구조와 Oracle Analytics가 생성하는 질의 로그를 이해해야 합니다.
선택 단계 최적화
선택 단계를 최적화하면 MDX 질의를 단순화하고 생성되는 MDX 질의 수를 줄여 성능을 향상시킬 수 있습니다.
다음 그림은 최적화된 선택 단계와 최적화되지 않은 선택 단계를 비교한 예를 보여줍니다.
CASE 문
CASE
문 기능은 MDX 질의에서 지원되지 않으며 항상 Oracle Analytics에서 적용되어야 합니다. CASE
문과 관련하여 이 섹션에서 설명된 논리는 MDX 질의에서 지원되지 않는 대부분의 함수(if null
등)에 대해 적합합니다.
CASE
문을 사용할 때 장점과 단점이 있습니다. 보고서 공식에 CASE
문을 포함하면 MDX 질의에 포함되지 않습니다. 이렇게 하면 MDX 질의가 간소화되고 성능이 향상될 수 있습니다. 그러나 반대급부로 효과적으로 필터링할 수 없으며, 이는 필요한 것보다 많은 레코드가 질의에 반환될 수 있음을 의미합니다.
CASE
문 기능을 사용할 때 제한사항은 다음과 같습니다.
CASE
문이 여러 멤버를 결합하지 않는 경우 문에 사용된 기본 열은 질의와 뷰에 숨겨진 별도 열로 포함되어야 합니다.CASE
문이 여러 멤버를 결합하는 경우 집계 레벨에 영향을 주지 않으면서 기본 열을 뷰에 포함할 수 없습니다. 다음과 같습니다.
SUM
, MAX
, MIN
)으로 변경해야 합니다. 이는 내부 집계 규칙을 사용하여 멤버를 결합하고 올바른 결과를 제공하는 경우에만 작동합니다.FILTER 함수
CASE
문 기능과 달리 FILTER
함수는 실행을 위해 데이터베이스로 전달될 수 있습니다.
보고서 공식에서 FILTER
함수를 사용할 때의 주요 이점은 선택 항목이 MDX 질의에 적용되고 데이터베이스에서 계산 및 검색되는 데이터의 양이 줄어든다는 것입니다.
FILTER
함수 사용의 주요 단점은 실행되는 MDX 질의 수가 늘어날 수 있다는 것입니다. 기본적으로 사용된 각 FILTER
함수에 대해 하나의 질의가 실행됩니다.
CASE와 FILTER 비교 예
이 예에서 사용자는 분기별 수익 및 선택된 제품 SKU를 보여주는 보고서를 요청합니다. 또한 SKU는 12개 범주로 그룹화됩니다. Other Cola 범주에는 LOB의 제품(Cola, Diet Cola, Shared Diet Cola 등)이 지정되어 있습니다.
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
CASE
문을 기반으로 하는 그룹화가 없습니다. Oracle Analytics에서 처리된 CASE
문을 사용하여 간단한 MDX 질의가 생성됩니다.
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]
CASE
문은 BI Server에서 실행되며 이는 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]
수익 측정항목에 대해 필터를 사용하여 필요한 LOB 멤버만 검색할 수도 있습니다. 이 시나리오에서는 해당 필터가 적용된 세 개의 측정항목을 생성합니다.
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
이 시나리오에서는 각 필터에 대해 하나씩 세 개의 질의가 생성되며 성능 문제가 발생합니다.
질의 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] ]]
질의 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] ]]
질의 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]
제품 필터 적용 예
더 나은 접근 방식은 필터 없이 단일 측정항목 열이 있는 보고서에 제품 열을 포함하는 것입니다. 그런 다음 필요한 제품을 포함하는 필터를 생성합니다. 제품을 다른 범주로 그룹화하려면 CASE
문을 사용합니다. 이 시나리오에서는 필터링된 행으로 생성되는 단일 MDX 질의가 생성되며 CASE
문이 Oracle Analytics에 의해 적용되더라도 모든 레코드가 아닌 데이터 부분 집합이 사용됩니다.
다음은 CASE
문이 성능 문제를 일으키는 다른 시나리오입니다.
개발자는 CASE
문을 적용하여 브랜드 이름을 바꾸며, 대시보드 프롬프트를 통해 사용자는 브랜드를 선택할 수 있습니다.
MDX에서는 CASE
문이 지원되지 않으므로 Brand2
에 대한 필터는 MDX 질의에서 적용될 수 없습니다. 모든 브랜드가 선택되었으며 최적화되지 않았습니다.
이 유형의 시나리오에서는 CASE
문을 제거하고 데이터베이스의 멤버 이름을 바꾸거나 별칭을 생성하는 것이 좋습니다.