Intro To MDX Aso

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 126

Intro to MDX + ASO

Gary Crisci Oracle Ace

#Kscope

Developing Essbase Applications

Like the best, most advanced Essbase conference there ever could be Advanced content Good practices Written by some of the most well known Essbase developers Source code at www.developingessbasebook.com You should buy it

#Kscope

What is ASO?

ASO stands for Aggregate Storage Option


Essbase applications have two distinct architecture/storage options

Aggregate Storage (ASO) Block Storage (BSO)

ASO was introduced in version 7 Initially ASO was seen as an alternative to large block storage applications with sparse data sets With enhancements over the last few releases, in many cases, ASO is now the default storage option choice for new applications

#Kscope

Differences Between ASO and BSO

Aggregate storage applications store data in tablespaces. Each application directory contains directories for four tablespaces:
default: Contains database data structure and database values (After data is loaded, the tablespace location cannot be changed.) log: contains a binary transactional log of all the updates that are made in the default tablespace metadata: contains information about the file locations, files, and objects contained in the database temp: Provides a temporary workspace to be used during operations such as data loads, aggregations, and retrievals

#Kscope

Differences Between ASO and BSO

BSO has Dense and Sparse dimensions

ASO has Dynamic and Stored hierarchies

#Kscope

ASO Hierarchies

Stored or Dynamic
Different concept than BSO

Cross use of terms causes confusion


Everything in ASO is Dynamic For best performance make everything stored

#Kscope

Stored vs. Dynamic

Lets clarify
A level 0 member with no formula, even though it is in a dynamic hierarchy, is still a stored member.

Upper level members are all "dynamic".

All level 0 members that do not have formulas are stored in ASO.

Upper level members of dynamic hierarchies are pure dynamic (i.e. calculated on retrieval).

Upper level members of stored hierarchies are candidates to have their values pre-aggregated and stored in a view.

#Kscope

Stored Hierarchies

Stored hierarchies are the default hierarchy setting and generally best performing.
Whenever possible, hierarchies should be set to Stored.

The limitations of stored hierarchies are that all members must be set to (+ or ~) for the consolidation operator.
If the consolidation operator is set to (~), the parent member must be set to Label Only.

You cannot have members with member formulas in a stored hierarchy, and you cannot have shared members in a stored hierarchy (unless the stored hierarchy is within a multiple hierarchies enabled dimension). There are also two limitations on label only members.
1) All dimension members at the same level as the member must be label only and 2) the parents of the member must be label only.

#Kscope

Stored Hierarchy - Label Only

Invalid

Valid

#Kscope

Dynamic Hierarchies
When we talk about dynamic hierarchies, we are talking about the way Essbase stores and optimizes the data

To evaluate a dynamic hierarchy, Essbase calculates, rather than aggregates, the members and formulas

The order in which members and formulas are evaluated is defined by the solve order property. #Kscope

Dynamic Hierarchies

At the time of retrieval, Essbase calculates the required member combinations and calculates any required outline member formulas. Because dynamic hierarchies are calculated, the data retrieval time may be longer than for data retrieved from stored hierarchies. However, when you design your database, dynamic hierarchies provide the following advantages:
1) They can contain any consolidation operator.

(+, -, *, /, %, ~)

(^) Not Supported

2) They can have formulas.

#Kscope

Stored vs. Dynamic

For best query performance, try to make as many hierarchies stored as possible
Stored hierarchies become candidates for pre-aggregation Queries will be much faster Can leverage query tracking to optimize aggregations

#Kscope

Multiple Hierarchies

Multiple hierarchies is a setting at the dimension level that allows for having both stored and dynamic hierarchies within the same dimension.

#Kscope

Accounts Dimension and Members on Dynamic Hierarchies


In Block Storage applications, Accounts dimension, has added functionality.
Accounts dimensions are enabled for Expense reporting, and Time Balancing.

Aggregate Storage applications do not support expense reporting.


Can simulate with UDA and conditional MDX statements (CASE and/or IF).

ASO Account dimension supports Time Balancing


There are limitations Often will need to consider alternative approach MDX Time Balancing Aggregating solutions using periodic balances instead of time balances

#Kscope

Accounts Dimension and Members on Dynamic Hierarchies

ASO Account dimensions are dynamic

By default, it is also set as the compression dimension This is not always a good thing!

Aggregate ASO applications do support Solve Order. storage Solve Order works in conjunction with applications do the MDX query language to determine calculation order when processing not support twomember formulas. pass calculations,

#Kscope

Shared Members and Alternate Hierarchies

#Kscope

Shared Member and Alternate Hierarchies

The alternate hierarchy has shared members that refer to non-shared members of previous hierarchies in the outline. The shared members roll up according to a different hierarchy from the non-shared members to which they refer. Shared members on dynamic hierarchies can have formulas.

#Kscope

Shared Member and Alternate Hierarchies

The following restrictions apply when creating alternate hierarchies in aggregate storage outlines:
The nonshared instance of the member must occur in the outline before any shared instances of the member. The first hierarchy in a dimension where multiple hierarchies are enabled cannot contain a shared member. Stored hierarchy dimensions cannot have shared members.

Stored hierarchies within a multiple hierarchies dimension can have shared members.

To ensure that values are not double-counted, a stored hierarchy cannot contain multiple copies of the same shared member. Nonshared instances of a member must be in the same dimension as the shared member (same for block storage outlines). A stored hierarchy cannot contain a nonshared instance and a shared instance of the same member. A stored hierarchy can contain a shared instance of a dynamic hierarchy member only if the dynamic hierarchy member is a level 0 member without a formula.

#Kscope

Attribute Dimensions

Attribute dimensions in aggregate storage databases:


Only the addition (+) consolidation operator is available for attribute dimensions. For a given attribute dimension, all associations must be with one level of the base dimension. The following restrictions apply to attribute associations:

Level 0: You can associate attributes with any level 0 member of a dynamic or stored hierarchy that does not have a formula. Non-level 0: You can associate attributes only to upper level members in the primary stored hierarchy.

#Kscope

Attribute Dimensions

Essbase considers queries on attribute dimensions when using query tracking


May include attribute dimension members in aggregate view selections.
#Kscope

Calculation Differences

#Kscope

Database Calculation

Database calculations are done in a block storage application by executing calculation scripts, as well as member formulas, and outline consolidation operators. The ability to materialize data via MDX scripts has been recently introduced in v 11.1.2 ASO database calculations are based on outline operators and member formulas. Aggregate storage applications are fully dynamic
data can be loaded into the database at level 0 and retrieved at upper levels with calculated results being returned.

To improve query performance, the database can be preaggregated by defining aggregate views.

#Kscope

Formulas

BSO applications leverage Essbase calculation script functions Aggregate storage databases utilize MDX functions.
There is no support for BSO functions in ASO, they must be converted to MDX. Refer to the Essbase technical reference
(http://download.oracle.com/docs/cd/E12825_01/epm.111/esb_techref/frameset. htm?launch.htm)

Contents Section: MDX->Aggregate Storage and MDX->MDX Outline Formulas.

#Kscope

Attribute Calculations Dimension

The attribute calculations dimension in block storage applications supports Sum, Count, Min, Max, and Average by default.

The attribute calculations dimension in aggregate storage applications only supports Sum.

#Kscope

Data Load Differences

#Kscope

Data Load Differences

Cells loaded through data loads


ASO : Only level 0 cells whose values do not depend on formulas in the outline are loaded BSO: Cells at all levels can be loaded (except Dynamic Calc and Label Only members)

Update of database values


ASO: At the end of a data load, if an aggregation exists, the values in the aggregation are recalculated BSO: No automatic update of values. To update data values, you must execute all necessary calculation scripts.

#Kscope

Data Load Differences


ASO: The loading of multiple data sources into aggregate storage databases is managed through temporary data load buffers. BSO: Not supported

Data load buffers

Atomic replacement of the contents of a database

ASO: When loading data, you can replace the contents of the database or the contents of all incremental data slices in the database. BSO: Not supported

#Kscope

Data Load Differences


ASO: Databases can contain multiple slices of data. Data slices can be merged. BSO: Not supported

Data slices

ASO: Full support for parent-child build method. Duplicate generation (DUPGEN) build method limited to building alternate hierarchies up to generation 2 Dimension build (DUPGEN2). for shared BSO: Support for all build methods members

#Kscope

Data Load Differences

Loading data mapped to dates

ASO: In a date-time dimension, you can load data into level-0 members using supported date-format strings instead of member names.

BSO: Date-time dimension type is not supported.

#Kscope

Query Differences

#Kscope

Query Differences

Report Writer
ASO: Supported, except for commands related to sparsity and density of data

Spreadsheet Add-in
ASO: Supported, with limited ability to change data (write-back)

BSO: Fully supported

BSO: Fully supported

#Kscope

Query Differences

API
ASO: Supported BSO: Supported

Export
ASO: Support with the following restrictions: Export of level 0 data only (no upper-level export) No columnar export BSO: Supported

#Kscope

Query Differences
ASO: Supported BSO: Supported
MDX queries

Queries on attribute members that are associated with nonlevel 0 members

ASO: Returns values for descendants of the non-level 0 member. BSO: Returns #MISSING for descendants of the non-level 0 member

#Kscope

Query Differences
Queries on attribute members and shared members ASO: A shared member automatically shares the attribute associations of its nonshared member BSO: A shared member does not share the attribute associations of its nonshared member

Query logging
ASO: Not supported BSO: Supported

#Kscope

Query Differences

Query performance
ASO: Considerations when querying data from a dimension that has multiple hierarchies. BSO: Hierarchies not relevant

#Kscope

Feature Differences

#Kscope

Feature Differences

Aliases

Currency conversion
ASO: Not supported BSO: Supported

ASO: Supported

BSO: Supported

#Kscope

Feature Differences

Data mining
ASO: Not supported BSO: Supported

Hybrid analysis
ASO: Support with the following restriction:

queries that contain a relational member and an Essbase member with a formula in the same query are not supported.

For example, if California is a relational member, and the member Profit has a formula, the following report script returns an error: Jan California Profit !

BSO: Supported

#Kscope

Feature Differences

Incremental data load

ASO: Supported
BSO: Supported ASO: Not supported

LROs

BSO: Supported #Kscope

Feature Differences

Time balance reporting Triggers

ASO: Support with the following restrictions: Skip Zeros is not supported Time dimension must contain at least one stored hierarchy Shared members must be at level zero BSO: Supported

ASO: After-update triggers supported BSO: On-update triggers and after-update triggers supported

#Kscope

Feature Differences

Unicode
ASO: Supported BSO: Supported

Variance reporting
ASO: Not supported Can work around with UDA and conditional MDX functions BSO: Supported

#Kscope

Feature Differences
Date-time dimension type and linked attribute dimensions

User ability to change data (write-back)

ASO: Supported

BSO: Fully supported

ASO:

BSO: Not supported

User write back to level 0, via data slices, is supported

#Kscope

Case Study: Converting a large BSO application to ASO

#Kscope

Existing BSO Model

#Kscope

Existing BSO Model


26 GB database
4 GB total .ind files 22 GB total .pag files

Processing Window
3 to 4 hours full overnight refresh 1 hour intraday update Cube is offline during this window Only update current year

#Kscope

ASO Challenges

Large Account dimension with (+) and () operators


To keep as is, would need to be Dynamic hierarchy

Too Slow!

Only way to make it feasible is for Account dim to be Stored

Limited to (+) and (~) only

Account dimension includes P&L and Balance Sheet


Balance Sheet values are time balanced

Overall functionality has to be same as existing model


Query time, look and feel Ability to query YTD values

#Kscope

ASO Solutions

Convert Account dimension to Stored Hierarchy


Remove Account tag Set all members to (+) Use accounting principles to convert accounts into Debit/Credit format

Tag Debit /Credit members with UDA flags Leverage load rules to FLIP values for tagged members upon data load Accounting View

Add a View dimension with two members


default load member with values in Debit/Credit format Calculated member that uses MDX formula to FLIP sign on Credit members

Management Reporting View

Load Periodic Balance Sheet values


Add a new Beginning Balance member to Time dimension Train users to pull Balances using YTD values

#Kscope

New ASO Model

#Kscope

New ASO Model vs. Existing BSO


26 GB application
4 GB ind files 22 GB pag files

2 GB application Processing Window


Full overnight

~15 minutes ~3-4 minutes


No down time

Full intraday

Processing Window

Full overnight refresh 3-4 Hours Intraday update 1 Hour Cube is offline during this window Only update current year

#Kscope

What is MDX?

#Kscope

MDX

Standardized query language for OLAP

Pros
Understands hierarchical relationships and member properties Can perform robust calculations utilizing many predefined functions

MultiDimensional Expression
#Kscope

Cons
Lacks formatting Can be problematic on large data sets Not well suited for general use by end users

How do we use MDX with Essbase?

There are two primary ways, at this time, to utilize MDX with Essbase.

Query data from an Essbase database


Aggregate Storage or Block Storage!

Construct member formulas


Aggregate Storage only

#Kscope

MDX Access

Users can execute MDX queries in variety of ways

MaxL EAS console

Third Party tools


#Kscope

MDX Basic Terms


AXIS SLICER CUBE SET WITH DIMENSION

LAYER MEMBER TUPLE FUNCTIONS PROPERTIES

#Kscope

MDX Basic Query

Proper Syntax

SELECT { [Year].Children } ON AXIS(0), { [Product].Children } ON AXIS(1), { [Market].Children } ON AXIS(2) FROM [Sample.Basic]; WHERE ( [Measures].[Sales],[Scenario].[Actual] )

#Kscope

MDX Basic Query

#Kscope

SQL
SELECT Product, Sales, Margin FROM dbo.Transactions WHERE Year = Jan

MDX
SELECT {[Measures].[Sales], [Measures].[Margin]} ON COLUMNS, {[Product].levels(0).members} ON ROWS FROM [SAMPASO].[BASIC] Must Define axis WHERE [Year].[Jan]
specification to render multi-dimensional in a report.

#Kscope

Nesting rows using AXIS


SELECT {[Measures].[Sales], [Measures].[Margin]} ON AXIS(0), {[Product].levels(0).members} ON AXIS(1), {[Scenario].children} ON AXIS(2) FROM [SAMPASO].[BASIC] WHERE [Year].[Jan]

Nesting columns is a little different


SELECT {CrossJoin({[Measures].[Sales], [Measures].[Margin]}, {[Scenario].children})} ON axis(0), {[Product].levels(0).members} ON axis(1) FROM [SAMPASO].[BASIC] WHERE [Year].[Jan]

#Kscope

Naming Conventions
The most obvious way to identify a member is to start with the name of the dimension and work downwards, specifying the members at each level in the hierarchy until we reach the required members - Fast Track to MDX, 2004, Whitehorn, Zare, Pasumansky [Jan] [Year].[Jan] [Year].[Qtr1].[Jan] * No difference in performance * Particularly important with Duplicate Member names * No quotes - Jan vs. [Jan]

#Kscope

Tuples
A tuple is defined as an intersection of exactly a single member from each dimension in the cube. For each dimension that is not explicitly referenced, the current member is implicitly added to the tuple definition. A tuple always identifies (or has the potential to identify) a single cell in the multi-dimensional matrix. That could be an aggregate or a leaf level cell, but nevertheless one cell and only one cell is ever implied by a tuple. - Fast Track to MDX, 2004, Whitehorn, Zare, Pasumansky ([Year].[Jan], [Measures].[Sales], [Product].[Cola], [Market].[East]) ([Year].[Jan], [Measures].[Sales]) ([Year].[Jan])

* Wrap tuples in parenthesis ( ) * Similar to a cross-dimensional operator (Jan->Sales) vs. ([Year].[Jan], [Measures].[Sales])

#Kscope

Sets
A set is a collection of tuples with the same dimensionality. It may have more than one tuple, but it can also have only one tuple, or even have zero tuples, in which case it is an empty set. - Fast Track to MDX, 2004, Whitehorn, Zare, Pasumansky {([Scenario].[Actual],[Measures].[Sales]), ([Scenario].[Actual],[Measures].[Margin])} {[Year].levels(0).members} {[Measures].[Sales]} Wrap sets in { }

#Kscope

We use MDX Expressions to define member formulas in the Essbase outline

Many functions can be translated from Essbase calc language to MDX


In EAS go to Help -> Information Map -> Technical Reference Go to MDX -> Aggregate Storage Topics -> MDX Outline Formulas For version 7X go to MAXL -> MDX

Same concept as member formulas in BSO

#Kscope

@AVG Calculator: @AVG(SKIPMISSING, @CHILDREN(East)); MDX: Avg([East].Children)

For SKIPNONE - Avg([East].Children,IncludeEmpty)

@CHILDREN Calculator: @CHILDREN(Market); MDX: Children([Market]) OR [Market].Children @ICHILDREN Calculator: @ICHILDREN(Market); MDX: Union({[Market]},{[Market].children}) @CURRMBR Calculator: @CURRMBR(Product); MDX: CurrentMember([Product]) or [Product].CurrentMember @LEVMBRS Calculator: @LEVMBRS(Product,0); MDX: [Product].levels(0).Members

#Kscope

@LSIBLINGS Calculator: @LSIBLINGS(Qtr4); MDX: MemberRange([Qtr4].FirstSibling, [Qtr4].Lag(1)) @RSIBLINGS Calculator: @RSIBLINGS(Qtr1); MDX: MemberRange([Qtr1].Lead(1), [Qtr1].LastSibling) @PARENTVAL Calculator: @PARENTVAL(Market, Sales); MDX: ([Sales], [Market].CurrentMember.Parent).Value @REMOVE Calculator: @REMOVE(@CHILDREN(East),@LIST(New York,Connectic MDX: Except({[East].Children}, {[New York], [Connecticut]}) @COUNT Calculator: @COUNT(SKIPMISSING, @RANGE(Sales, Children(Product MDX:NonEmptyCount(CrossJoin({[Sales]},{[Product].Children})) For SKIPNONE Count([Product].Children)

#Kscope

@IDESCENDANTS Calculator: @IDESCENDANTS(Market); MDX: Descendants([Market]) @DESCENDANTS Calculator: @DESCENDANTS(Market,0); MDX: Descendants([Market], [Market].levels(0)) OR

Leaves([Market]) New in Sys 9

@ISICHILD Calculator: @ISICHILD(South); MDX: IIF(Is([Market].CurrentMember,[South]) OR IsChild([Market].CurrentMember,[South]), <True>,<False>)

#Kscope

Time Functionality
Leverage Analytic Dimensions

#Kscope

Period To Date Functions

[QTD] = SUM( PeriodsToDate( [Year].Generations(2), [Year].CurrentMember ), [View].[Per] ) [YTD] = SUM( PeriodsToDate( [Year].Generations(1), [Year].CurrentMember ), [View].[Per] )

#Kscope

Time Balancing

CASE WHEN IsUDA([Measures].CurrentMember, "TB_Last") THEN IIF(IsLeaf([Year].CurrentMember), [View].[Per], (ClosingPeriod ([Year].Levels(0), [Year].CurrentMember), [View].[Per]) ELSE [View].[Per] END

Before

After

#Kscope

Formula Precedence (Solve Order)


The mechanism that standard MDX uses for dealing with dimensional formula precedence is called solve order. Every calculated member has an associated solve order number, which is an integer that says what the calculation priority of the member is. When calculated members overlap on a cell, the member with the highest solve order number wins and is used to calculate the cell.
-

MDX Solutions, 2006, Spofford, Harinath, Webb, Huang, Civardi Multiple members can have the same solve order

- Default value is 0, maximum value is 127


-

- Dimension solve order sets the default solve order for all members in the dimension, you can still edit individual members order if required - In addition to calculating the correct value, Solve Order can yield huge performance gains for calculated members

- Test, Test, Test!!!

#Kscope

Recap MDX is a powerful query language, similar to SQL, that is used with multi-dimensional databases. MDX can be used to query both ASO and BSO Essbase cubes, although it is primarily used for member expressions in ASO models. Most Essbase calculator functions can be converted to MDX expressions refer to the technical document. You can leverage MDX expression in ASO cubes to simulate Dynamic Time Series (Period To Date) and Time Balancing functionality. Solve Order is extremely important, both for calculating accurate results and performance.

#Kscope

MDX as Administrator Tool

Provides a framework for administrators to analyze meta-data. Easily generates member lists based on parameters to filter results Apply complex logic to find answers to questions that can be used to troubleshoot or optimize an outline

#Kscope

Set dml_output options

#Kscope

Return All Members


Axis-1 +------------------------------------(Year) (Qtr1) (Jan) (Feb) (Mar) (Qtr2) (Apr) (May) (Jun) (Qtr3) (Jul) (Aug) (Sep) (Qtr4) (Oct) (Nov) (Dec)

alter session set dml_output alias off; set column_width 50;

SELECT {} ON AXIS(0),

[Year].Members ON AXIS(1)
FROM [Sample.Basic];

/*OR*/

SELECT {} ON AXIS(0), Members([Year]) ON AXIS(1) FROM [Sample.Basic];

#Kscope

Return All Children

SELECT {} ON AXIS(0),
{[Year].[Qtr1].Children} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +--------------(Jan) (Feb) (Mar)

/*OR*/

SELECT {} ON AXIS(0), {[Qtr1].Children} ON AXIS(1) FROM [Sample.Basic];

#Kscope

Combining two sets


SELECT {} ON AXIS(0), {[Qtr1].Children, [Qtr2].Children} ON AXIS(1) FROM [Sample.Basic];
Axis-1 +--------------------------------------(Jan) (Feb) (Mar) (Apr) (May) (Jun) Axis-1 +------------------------------------------------(Year) (Qtr1) (Jan) (Feb) (Mar) (Qtr2) (Apr) (May) (Jun) (Qtr3) (Jul) (Aug) (Sep) (Qtr4) (Oct) (Nov) (Dec) (Qtr1) (Qtr2) (Qtr3) (Qtr4)

/*Beware of Duplicates*/

SELECT {} ON AXIS(0), {[Year].Members, [Year].Children} ON AXIS(1) FROM [Sample.Basic];

#Kscope

Combining two sets

/*Removes Duplicates*/ SELECT {} ON AXIS(0), Distinct({[Year].Members, [Year].Children}) ON AXIS(1) FROM [Sample.Basic]; /*OR*/ SELECT {} ON AXIS(0), Union({[Year].Members}, {[Year].Children}) ON AXIS(1) FROM [Sample.Basic];

/* ALL keyword will keep duplicates*/ SELECT {} ON AXIS(0), Union({[Year].Members}, {[Year].Children}, ALL) ON AXIS(1) FROM [Sample.Basic];

Axis-1 +---------(Year) (Qtr1) (Jan) (Feb) (Mar) (Qtr2) (Apr) (May) (Jun) (Qtr3) (Jul) (Aug) (Sep) (Qtr4) (Oct) (Nov) (Dec)

#Kscope

Axis-1 +------(Year) (Qtr1) (Jan) (Feb) (Mar) (Qtr2) (Apr) (May) (Jun) (Qtr3) (Jul) (Aug) (Sep) (Qtr4) (Oct) (Nov) (Dec) (Qtr1) (Qtr2) (Qtr3) (Qtr4)

Descendants including member


/*Descendants, including member*/ SELECT {} ON AXIS(0), {Descendants([Year])} ON AXIS(1) FROM [Sample.Basic]; Axis-1 +-------------------------------------(Year) (Qtr1) (Jan) (Feb) (Mar) (Qtr2) (Apr) (May) (Jun) (Qtr3) (Jul) (Aug) (Sep) (Qtr4) (Oct) (Nov) (Dec)

#Kscope

Descendants excluding member


/*Descendants, excluding member*/ SELECT {} ON AXIS(0), Except({Descendants([Year])}, {[Year]}) ON AXIS(1) FROM [Sample.Basic]; Axis-1 +-------------------------------------(Qtr1) (Jan) (Feb) (Mar) (Qtr2) (Apr) (May) (Jun) (Qtr3) (Jul) (Aug) (Sep) (Qtr4) (Oct) (Nov) (Dec)

#Kscope

Descendants of a member mid-dimension

/*Descendants of a member mid-dimension*/

SELECT {} ON AXIS(0), {Descendants([Qtr1])} ON AXIS(1)

Axis-1 +---------(Qtr1) (Jan) (Feb) (Mar)

FROM [Sample.Basic];

#Kscope

Descendants Level 1 only

/*Only level 1 descendants*/

SELECT {} ON AXIS(0), {Descendants([Year], 1)} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +----------(Qtr1) (Qtr2) (Qtr3) (Qtr4)

#Kscope

Descendants Level 0 only


/*Only level 0 descendants*/ Axis-1 +------(Jan) (Feb) (Mar) (Apr) (May) (Jun) (Jul) (Aug) (Sep) (Oct) (Nov) (Dec)

SELECT {} ON AXIS(0), {Descendants([Year], Levels([Year],0))} ON AXIS(1) FROM [Sample.Basic];

#Kscope

Ancestors - up two levels


/*Ancestors, up 2 levels*/ SELECT {} ON AXIS(0), {Ancestors([Jan], 2)} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +-------(Year) (Qtr1) (Jan)

#Kscope

Ancestors - up one level


/*Ancestors, up 1 level*/ SELECT {} ON AXIS(0), {Ancestors([Jan], 1)} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +------(Qtr1) (Jan)

#Kscope

Ancestors - up zero levels


/*Ancestors up 0 levels*/ /*Only returns the member, but it is valid*/ SELECT {} ON AXIS(0), {Ancestors([Jan], 0)} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +-------(Jan)

#Kscope

Ancestors - up 100 levels


/*Ancestors up 100 levels*/ /*Doesn't matter that there are Less than 100 levels, still valid*/ SELECT {} ON AXIS(0), {Ancestors([Jan], 100)} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +-------(Year) (Qtr1) (Jan)

#Kscope

Cousins

/*Returns a child member at the same position as a member from another ancestor*/
Axis-1 +---------(May)

SELECT {} ON AXIS(0), {Cousin([Qtr1].[Feb], [Qtr2])} ON AXIS(1) FROM [Sample.Basic];

#Kscope

Level 0 members
/*all level 0 members of dimension*/

SELECT {} ON AXIS(0), {[Year].levels(0).members} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +------(Jan) (Feb) (Mar) (Apr) (May) (Jun) (Jul) (Aug) (Sep) (Oct) (Nov) (Dec)

#Kscope

Level 0 descendants of a member

/*level 0 descendants of a member*/


SELECT {} ON AXIS(0), {Descendants([Time].[1st Half], [Time].levels(0), SELF)} ON AXIS(1)

FROM [ASOSamp.Sample];

Axis-1 +-----(Jan) (Feb) (Mar) (Apr) (May) (Jun)

/*OR*/

SELECT {} ON AXIS(0), {Descendants([Time].[1st Half], 100, LEAVES)} ON AXIS(1) FROM [ASOSamp.Sample];

#Kscope

What Level ?

/*list of all members with levels*/ SELECT {} ON AXIS(0), [Time].members DIMENSION PROPERTIES [Time].[LEVEL_NUMBER] ON AXIS(1) FROM [ASOsamp.Sample];
Axis-1 Axis-1.properties +-------------------------------------------------+------------------------------------------------(Time) (LEVEL_NUMBER = 4, type: ULONG, ) (MTD) (LEVEL_NUMBER = 3, type: ULONG, ) (1st Half) (LEVEL_NUMBER = 2, type: ULONG, ) (Qtr1) (LEVEL_NUMBER = 1, type: ULONG, ) (Jan) (LEVEL_NUMBER = 0, type: ULONG, ) (Feb) (LEVEL_NUMBER = 0, type: ULONG, ) (Mar) (LEVEL_NUMBER = 0, type: ULONG, ) (Qtr2) (LEVEL_NUMBER = 1, type: ULONG, ) (Apr) (LEVEL_NUMBER = 0, type: ULONG, ) (May) (LEVEL_NUMBER = 0, type: ULONG, ) (Jun) (LEVEL_NUMBER = 0, type: ULONG, ) (2nd Half) (LEVEL_NUMBER = 2, type: ULONG, ) (Qtr3) (LEVEL_NUMBER = 1, type: ULONG, ) (Jul) (LEVEL_NUMBER = 0, type: ULONG, ) (Aug) (LEVEL_NUMBER = 0, type: ULONG, ) (Sep) (LEVEL_NUMBER = 0, type: ULONG, ) (Qtr4) (LEVEL_NUMBER = 1, type: ULONG, ) (Oct) (LEVEL_NUMBER = 0, type: ULONG, ) (Nov) (LEVEL_NUMBER = 0, type: ULONG, ) (Dec) (LEVEL_NUMBER = 0, type: ULONG, )

#Kscope

What Level ?

/*Specific member with level*/


SELECT {} ON AXIS(0), {[Qtr1]} DIMENSION PROPERTIES [Time].[LEVEL_NUMBER] ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 Axis-1.properties +-------------------------------------------------+------------------------------------------------(Qtr1) (LEVEL_NUMBER = 1, type: ULONG, )

#Kscope

Members on same level

/*Return Members on same level*/


SELECT {} ON AXIS(0), {[Time].[Qtr1].Level.members} ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +---------(Qtr1) (Qtr2) (Qtr3) (Qtr4)

#Kscope

What Generation ?

/*list of all members with Generation*/ SELECT {} ON AXIS(0), [Time].members DIMENSION PROPERTIES [Time].[GEN_NUMBER] ON AXIS(1) FROM [ASOsamp.Sample];
Axis-1 Axis-1.properties +-------------------------------------------------+------------------------------------------------(Time) (GEN_NUMBER = 1, type: ULONG, ) (MTD) (GEN_NUMBER = 2, type: ULONG, ) (1st Half) (GEN_NUMBER = 3, type: ULONG, ) (Qtr1) (GEN_NUMBER = 4, type: ULONG, ) (Jan) (GEN_NUMBER = 5, type: ULONG, ) (Feb) (GEN_NUMBER = 5, type: ULONG, ) (Mar) (GEN_NUMBER = 5, type: ULONG, ) (Qtr2) (GEN_NUMBER = 4, type: ULONG, ) (Apr) (GEN_NUMBER = 5, type: ULONG, ) (May) (GEN_NUMBER = 5, type: ULONG, ) (Jun) (GEN_NUMBER = 5, type: ULONG, ) (2nd Half) (GEN_NUMBER = 3, type: ULONG, ) (Qtr3) (GEN_NUMBER = 4, type: ULONG, ) (Jul) (GEN_NUMBER = 5, type: ULONG, ) (Aug) (GEN_NUMBER = 5, type: ULONG, ) (Sep) (GEN_NUMBER = 5, type: ULONG, ) (Qtr4) (GEN_NUMBER = 4, type: ULONG, ) (Oct) (GEN_NUMBER = 5, type: ULONG, ) (Nov) (GEN_NUMBER = 5, type: ULONG, ) (Dec) (GEN_NUMBER = 5, type: ULONG, )

#Kscope

What Generation ?

/*Specific member with Generation*/ SELECT {} ON AXIS(0), {[Qtr1]} DIMENSION PROPERTIES [Time].[GEN_NUMBER] ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 Axis-1.properties +-------------------------------------------------+------------------------------------------------(Qtr1) (GEN_NUMBER = 4, type: ULONG, )

#Kscope

Members on same generation

/*Return Members on same Generation*/


SELECT {} ON AXIS(0), {[Time].[Qtr1].Generation.members} ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +--------(Qtr1) (Qtr2) (Qtr3) (Qtr4)

#Kscope

Counts

/*Count level 0 member*/ WITH MEMBER [Year].[YearMemberCount] AS 'Count([Year].Levels(0).members)' SELECT {[Measures]} ON AXIS(0), {[Year].[YearMemberCount]} on AXIS(1) FROM Sample.Basic;
Axis-1 (Measures) +-------------------------------------------------+------------------------------------------------(YearMemberCount) 12

#Kscope

Count children

/*Count children*/ WITH MEMBER [Year].[YearChildrenCount] AS 'Count([Year].Children)'

SELECT {[Measures]} ON AXIS(0),


{[Year].[YearChildrenCount]} on AXIS(1) FROM Sample.Basic;

Axis-1 (Measures) +-------------------------------------------------+------------------------------------------------(YearChildrenCount) 4

#Kscope

Count descendants

/*Count descendants*/ WITH MEMBER [Year].[YearDescCount] AS 'Count(Descendants([Year]))' SELECT {[Measures]} ON AXIS(0), {[Year].[YearDescCount]} on AXIS(1) FROM Sample.Basic;

Axis-1 (Measures) +-------------------------------------------------+------------------------------------------------(YearDescCount) 17

#Kscope

Using Counts with filters

/*Members with one child*/


SELECT {} ON AXIS(0), {FILTER({[Stores].members}, COUNT({[Stores].CurrentMember.Children}, IncludeEmpty) = 1)} ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +---------(Electronic Essentials) (Club Electronics)

#Kscope

Using Counts with filters

/*Members with more than 100 children*/


SELECT {} ON AXIS(0), {FILTER({[Geography].members}, COUNT({[Geography].CurrentMember.Children}, IncludeEmpty) > 100)} ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +--------(CO) (KS) (NE) (IA) (IL) (IN) (MI) (MN) (MO) (OH) (WI) (MA) (ME) (NJ) (NY) (PA) (AL) (AR) (FL) (GA) (KY) (LA) (MD) (MS) (NC) (SC) (TN) (VA) (WV) (OK) (TX) (CA) (OR) (WA)

#Kscope

Criteria based member search

/*Stores starting with "801"*/


SELECT {} ON AXIS(0), {FILTER({[Geography].members}, Substring([Geography].CurrentMember.Member_Name, 1, 3) = "801")} ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +----------(80101) (80107) (80154) (80116) (80126) (80117) (80118) (80124) (80160) (80161) (80163) (80165) (80135)

#Kscope

Criteria based member search

/*Area Codes ending with "25"*/


SELECT {} ON AXIS(0), {FILTER({[Area Code].levels(0).members}, Substring([Area Code].CurrentMember.Member_Name, Len([Area Code].CurrentMember.Member_Name ) - 1) = "25")} ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +-------(225) (425) (925)

#Kscope

Criteria based member search

/*Products with word "Digital"*/ SELECT {} ON AXIS(0), {FILTER({[Products].members}, InStr(1, [Products].CurrentMember.Member_Name, "Digital", 1) > 0)} ON AXIS(1) FROM [ASOsamp.Sample]; /*Distinct Products with word "Digital", doesn't work, Shared members are not duplicates!*/ SELECT {} ON AXIS(0), Distinct({FILTER({[Products].members}, InStr(1, [Products].CurrentMember.Member_Name, "Digital", 1) > 0)}) ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +--------------------------(Digital Cameras/Camcorders) (Digital Cameras) (Digital Recorders) (Digital Recorders)

#Kscope

Using Distinct

/*An example where Distinct works*/ SELECT {} ON AXIS(0), {[Time].levels(0).members, [Qtr1].Children} ON AXIS(1) FROM [ASOsamp.Sample]; /*vs*/ SELECT {} ON AXIS(0), Distinct({[Time].levels(0).members, [Qtr1].Children}) ON AXIS(1) FROM [ASOsamp.Sample];

Axis-1 +---------(Jan) (Feb) (Mar) (Apr) (May) (Jun) (Jul) (Aug) (Sep) (Oct) (Nov) (Dec) (Jan) (Feb) (Mar)

Axis-1 +---------(Jan) (Feb) (Mar) (Apr) (May) (Jun) (Jul) (Aug) (Sep) (Oct) (Nov) (Dec)

#Kscope

Member Properties

/* 0 1 2 3

= = = =

Member (Non-Measure) Dimension Root member Member with Formula Measure */

SELECT {} ON AXIS(0), [Measures].levels(0).members DIMENSION PROPERTIES MEMBER_TYPE ON AXIS(1) FROM ASOsamp.Sample;

Axis-1 Axis-1.properties +-------------------------------------------------+------------------------------------------------(Original Price) (MEMBER_TYPE = 3, type: ULONG, ) (Price Paid) (MEMBER_TYPE = 3, type: ULONG, ) (Returns) (MEMBER_TYPE = 3, type: ULONG, ) (Units) (MEMBER_TYPE = 3, type: ULONG, ) (Transactions) (MEMBER_TYPE = 3, type: ULONG, ) (Avg Units/Transaction) (MEMBER_TYPE = 2, type: ULONG, ) (% of Total) (MEMBER_TYPE = 2, type: ULONG, ) (Test) (MEMBER_TYPE = 2, type: ULONG, )

#Kscope

Filter member properties

/*Filter by Members with formulas*/

SELECT {} ON AXIS(0), {FILTER([Measures].levels(0).members, [Measures].CurrentMember.MEMBER_TYPE = 2)} ON AXIS(1) FROM ASOsamp.Sample;

Axis-1 +---------------------(Avg Units/Transaction) (% of Total) (Test)

#Kscope

Member Properties

/*Expense Members*/ SELECT {} ON AXIS(0), [Measures].members DIMENSION PROPERTIES [Measures].[IS_EXPENSE] ON AXIS(1) FROM Sample.Basic;
Axis-1 Axis-1.properties +-------------------------------------------------+------------------------------------------------(Measures) (IS_EXPENSE = FALSE, type: BOOL, ) (Profit) (IS_EXPENSE = FALSE, type: BOOL, ) (Margin) (IS_EXPENSE = FALSE, type: BOOL, ) (Sales) (IS_EXPENSE = FALSE, type: BOOL, ) (COGS) (IS_EXPENSE = TRUE, type: BOOL, ) (Total Expenses) (IS_EXPENSE = TRUE, type: BOOL, ) (Marketing) (IS_EXPENSE = TRUE, type: BOOL, ) (Payroll) (IS_EXPENSE = TRUE, type: BOOL, ) (Misc) (IS_EXPENSE = TRUE, type: BOOL, ) (Inventory) (IS_EXPENSE = FALSE, type: BOOL, ) (Opening Inventory) (IS_EXPENSE = TRUE, type: BOOL, ) (Additions) (IS_EXPENSE = TRUE, type: BOOL, ) (Ending Inventory) (IS_EXPENSE = TRUE, type: BOOL, ) (Ratios) (IS_EXPENSE = FALSE, type: BOOL, ) (Margin %) (IS_EXPENSE = FALSE, type: BOOL, ) (Profit %) (IS_EXPENSE = FALSE, type: BOOL, ) (Profit per Ounce) (IS_EXPENSE = FALSE, type: BOOL, ) (Test) (IS_EXPENSE = FALSE, type: BOOL, ) (var) (IS_EXPENSE = FALSE, type: BOOL, ) (Flag) (IS_EXPENSE = FALSE, type: BOOL, ) (Test2) (IS_EXPENSE = FALSE, type: BOOL, )

#Kscope

Filter member properties

/*Expense members*/

SELECT {} ON AXIS(0), {FILTER([Measures].members, IsAccType([Measures].CurrentMember, Expense))} ON AXIS(1) FROM Sample.Basic;

Axis-1 +----------------(COGS) (Total Expenses) (Marketing) (Payroll) (Misc) (Opening Inventory) (Additions) (Ending Inventory)

#Kscope

Filter Text Attributes

/* Text Attribute */ SELECT {} ON AXIS(0), Attribute([Bottle]) ON AXIS(1) FROM [Sample.Basic];

/* OR */

Axis-1 +-----------(100-30) (200-10) (200-20) (200-30) (200-40) (300-10) (300-20) (400-10) (400-20) (400-30) Axis-1 +-----------(100-10) (100-20) (300-30)

SELECT {} ON AXIS(0),

Withattr([Pkg Type], "==", "Can") ON AXIS(1)


FROM [Sample.Basic];

#Kscope

Filter Boolean Attributes

/* Boolean Attribute */ SELECT {} ON AXIS(0), {FILTER({[Product].members}, [Product].CurrentMember.[Caffeinated])} ON AXIS(1) FROM [Sample.Basic]; /*OR*/ SELECT {} ON AXIS(0), Withattr([Caffeinated], "==", "TRUE") ON AXIS(1) FROM [Sample.Basic]; /*Opposite*/ SELECT {} ON AXIS(0), Withattr([Caffeinated], "==", "FALSE") ON AXIS(1) FROM [Sample.Basic];

Axis-1 +----------(100-10) (100-20) (200-10) (200-20) (300-10) (300-20) (300-30)


Axis-1 +----------(100-30) (200-30) (200-40) (400-10) (400-20) (400-30)

#Kscope

Filter Boolean Attributes

/* this won't work */ SELECT {} ON AXIS(0), Attribute([True]) ON AXIS(1) FROM [Sample.Basic]; /*OR*/ SELECT {} ON AXIS(0), Attribute([Caffeinated].[True]) ON AXIS(1)

ERROR - 1260046 - Unknown Member True used in query.

FROM [Sample.Basic];

#Kscope

Filter Date Attributes

/* Date attribute */ /*This won't work*/ SELECT {} ON AXIS(0), Withattr([Intro Date], "==", "04-01-1996") ON AXIS(1) FROM [Sample.Basic];

ERROR - 1001078 - Cannot form a valid attribute value from [04-01-1996].

/*This is correct*/

SELECT {} ON AXIS(0),
Withattr([Intro Date], "==", Todate("mm-dd-yyyy", "04-01-1996")) ON AXIS(1) FROM [Sample.Basic];

Axis-1 +---------------------------------(100-20) (100-30)

#Kscope

Filter Numeric Attributes

/* Numeric Attribute */

SELECT {} ON AXIS(0), Withattr([Population], ">=", 12000000) ON AXIS(1) FROM [Sample.Basic];

Axis-1 +-------------(New York) (Florida) (California) (Texas) (Illinois) (Ohio)

#Kscope

Filter UDA

SELECT {} ON AXIS(0), {Uda([Market], "Major Market")} ON AXIS(1) FROM [Sample.Basic];

/*OR*/

SELECT {} ON AXIS(0), {FILTER({[Market].members}, IsUda([Market].CurrentMember, "Major Market"))} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +--------------(East) (New York) (Massachusetts) (Florida) (California) (Texas) (Central) (Illinois) (Ohio) (Colorado)

#Kscope

Filter UDA

/*Combine Functions to further define filter*/

SELECT {} ON AXIS(0), {FILTER({Uda([Market], "Major Market")}, Substring( [Market].CurrentMember.Member_Name, 1, 3) = "New")} ON AXIS(1) FROM [Sample.Basic];

Axis-1 +-----------(New York)

#Kscope

Count UDA

/* count members with a UDA */ WITH MEMBER [Market].[MajorMarketCount] AS 'Count(UDA([Market], "Major Market")) SELECT {[Measures]} ON AXIS(0), {[Market].[MajorMarketCount]} on AXIS(1) FROM Sample.Basic;

Axis-1 (Measures) +-------------------------------------------------+------------------------------------------------(MajorMarketCount) 10

#Kscope

Count Attribute

/* Count members with specific attribute */ WITH MEMBER [Geography].[AreaCode719Count] AS 'Count(Withattr([Area Code], "==", "719")) SELECT {[Measures]} ON AXIS(0), {[Geography].[AreaCode719Count]} on AXIS(1) FROM ASOSamp.Sample;

Axis-1 (Measures) +-------------------------------------------------+------------------------------------------------(AreaCode719Count) 50

#Kscope

Count Attribute

/* Count mebers with attribute and members without attribute */ WITH MEMBER [Geography].[NoAttribute] AS 'Count(Filter( [Geography].levels(0).members, NOT IsValid([Geography].CurrentMember.[Area Code]))) MEMBER [Geography].[HasAttribute] AS 'Count(Filter( [Geography].levels(0).members, IsValid([Geography].CurrentMember.[Area Code]))) MEMBER [Geography].[Lev0Count] AS 'Count([Geography].Levels(0).members) SELECT {[Measures]} ON AXIS(0), {[Geography].[NoAttribute], [Geography].[HasAttribute], [Geography].[Lev0Count]} on AXIS(1) FROM ASOSamp.Sample;

Axis-1 (Measures) +-------------------------------------------------+------------------------------------------------(NoAttribute) 1 (HasAttribute) 9397 (Lev0Count) 9398

#Kscope

Filter members missing attribute

SELECT {} ON AXIS(0), {Filter( [Geography].levels(0).members, NOT IsValid([Geography].CurrentMember.[Area Code]))} on AXIS(1) FROM ASOSamp.Sample;

Axis-1 +--------(80101)

#Kscope

Finding Shared or Duplicate Members

WITH MEMBER [Measures].[Products_SharedMembers] AS 'Count( Generate({[Products].CurrentMember} AS [var1], Generate(Filter([Products].Levels(0).Members, [Products].CurrentMember.[MEMBER_NAME] = [var1].Item(0).Item(0).[MEMBER_NAME]), Filter ({[Products].CurrentMember}, IsAncestor( [Products], [Products].CurrentMember )))))' SELECT {[Measures].[Products_SharedMembers]} ON AXIS(0), [Products].Levels(0).Members ON AXIS(1) FROM [ASOSamp.Sample];

(Digital Cameras) (Camcorders) (Photo Printers) (Handhelds) (Memory) (Other Accessories) (Boomboxes) (Radios) (Direct View) (Projection TVs) (Flat Panel) (HDTV) (Stands) (Home Theater) (HiFi Systems) (Digital Recorders) (DVD) (Desktops) (Notebooks) (Displays) (CD/DVD drives) (Flat Panel) (HDTV) (Digital Recorders) (Notebooks)

1 1 1 1 1 1 1 1 1 1 2 2 1 1 1 2 1 1 2 1 1 2 2 2 2

*Code adapted from an example given by George Spofford

#Kscope

Finding Shared or Duplicate Members

WITH MEMBER [Measures].[Products_SharedMembers] AS 'Count( Generate({[Products].CurrentMember} AS [var1], Generate(Filter([Products].Levels(0).Members, [Products].CurrentMember.[MEMBER_NAME] = [var1].Item(0).Item(0).[MEMBER_NAME]), Filter({[Products].CurrentMember}, IsAncestor([Products], [Products].CurrentMember)))))' SELECT {} ON AXIS(0), Filter( Except([Products].Levels(0).Members, Descendants([All Merchandise])), [Measures].[Products_SharedMembers] > 1) ON AXIS(1) FROM [ASOSamp.Sample];

Axis-1 +------------(Flat Panel) (HDTV) (Digital Recorders) (Notebooks)

*Code adapted from an example given by George Spofford

#Kscope

Sparse Optimization using MDX

Conventional wisdom has been to order outline from smallest sparse dimension to largest sparse dimension. Edward Roske has said
The smallest to largest sparse is a total misnomer. What you actually want to do is make sure the number of blocks grows as slowly as possible (so make them in order of lowest ratio of parents:children)

#Kscope

Sparse Optimization using MDX

Using Sample.Basic as an example


Market dimension has 25 stored members Product dimension has 18 stored members

Conventional wisdom says that the Product Dimension would come before the Market dimension in outline order in order to adhere to the hour glass.

#Kscope

Sparse Optimization using MDX

To keep things simple, lets assume that all members are stored and there is one primary hierarchy
(Ive removed the Diet alternate rollup)

If we use the logic that


All members are parent, except for level 0 members AND All members are Children, except for the dimension root member

Then we can construct an MDX query to calculate the parent to child ratio

#Kscope

Sparse Optimization using MDX


WITH MEMBER [Measures].[Market Ratio] AS '(COUNT([Market].members) - COUNT([Market].levels(0).members)) / (COUNT([Market].members) - 1)' MEMBER [Measures].[Product Ratio] AS '(COUNT(Except([Product].members, Descendants([Diet]))) COUNT(Except([Product].levels(0).members, Descendants([Diet])))) / (COUNT(Except([Product].members, Descendants([Diet]))) - 1)' SELECT {[Year]} ON AXIS(0), { [Measures].[Market Ratio], [Measures].[Product Ratio] } ON AXIS(1) FROM [Sample.Basic];
Axis-1 ---------------------------------------------------------(Market Ratio) 0.208333333333333 (Product Ratio) 0.294117647058824

#Kscope

Sparse Optimization using MDX

Market Dimension has more stored members than Product


25 vs. 18

However the Parent:Child ratio of Market is smaller than the Parent:Child ratio of Product
.21 vs. .29

In order to optimize calc time, in theory, Market should come before Product in the outline Youre mileage may vary!
#Kscope

Questions

There are three types of answers


Good Fast Cheap

You can choose two!


#Kscope

You might also like