Showing posts with label MDX. Show all posts
Showing posts with label MDX. Show all posts

Friday, January 18, 2013

using mdx order purely based on measure

Wonder how to do an "ORDER by" purely based on a measure value! Well yes, it can simply be done using the ORDER function. 

Any idea what the result of this query would be?

SELECT
      [Measures].[Reseller Sales Amount] ON COLUMNS,
      ORDER
      (
            NONEMPTY
            (
                  {
                        [Geography].[Country].[Country]*
                        [Geography].[State-Province].[State-Province]
                  },
                  [Measures].[Reseller Sales Amount]
            ),
            [Measures].[Reseller Sales Amount],DESC  
      ) ON ROWS
FROM
      [Adventure Works]

Here is the result

I am sure this is not what you wanted...As you see they are not sorted yet.

To solve this all that you need to change in the above query is replace the DESC with BDESC (Break Hierarchy Desc)

SELECT
      [Measures].[Reseller Sales Amount] ON COLUMNS,
      ORDER
      (
            NONEMPTY
            (
                  {
                        [Geography].[Country].[Country]*
                        [Geography].[State-Province].[State-Province]
                  },
                  [Measures].[Reseller Sales Amount]
            ),
            [Measures].[Reseller Sales Amount],BDESC  
      ) ON ROWS
FROM
      [Adventure Works]

Here is result


Tuesday, March 6, 2012

Average of Dimension attribute value

Query to find out the average of a value that is an attribute in the dimension.


WITH
MEMBER [Measures].[Average Cars Owned] As
      Avg(
            DESCENDANTS(
                  [Customer].[Customer Geography].CURRENTMEMBER,
                  [Customer].[Customer Geography].[Customer]
            )AS Set1,
            StrToValue(Set1.Current.Properties("Number of Cars Owned"))
      )
     
SELECT
    {
            [Measures].[Average Cars Owned]
    } ON COLUMNS,
    DESCENDANTS(
    [Customer].[Customer Geography].CURRENTMEMBER,
    [Customer].[Customer Geography].[State-Province],SELF_AND_BEFORE)
     ON ROWS
FROM
   [Adventure Works]
where
[Customer].[Country].&[Australia]

Monday, July 4, 2011

MDX: Query Performance ORDER using Val

One of my colleague working on a different project had an issue with performance of a query. The query looked something like this

Key Points: It has an ORDER BY clause, the member used for ordering converts the string value to Int using Val.

SELECT 
{[Measures].[Answer Count]} on 0,
NONEMPTY(
[Location].[Region].[Region] *
ORDER
(
[Location].[Retailer Store Number].[Retailer Store Number],
Val(
[Location].[Retailer Store Number].CURRENTMEMBER. MEMBER_CAPTION
)
)
,[Measures].[Answer Count]) on 1
FROM 
cdw 
WHERE [Visit Start Date].[Last 1 Week].&[Current Week]


Limitations 
One does not have permission to change the structure of the cube as client has not given permission. Data type for store number is String. The store number stores integer values but yet the data type is String. Hence ORDER was @MDX

Initially I suggested the following

WITH
SET OrderedStoreNum AS
Order
(
    NonEmpty
    (
        [Location].[Retailer Store Number].[Retailer Store Number]
        ,[Measures].[Answer Count]
    )
    ,Cint([Location].[Retailer Store Number].CurrentMember.Member_Caption)
    ,BASC
)
SELECT
{
    [Measures].[Answer Count]
} ON 0
,NonEmpty
(
    [Location].[Region].[Region] * OrderedStoreNum
    ,[Measures].[Answer Count]
) ON 1
FROM [cdw];

Reasoning for above suggestion was as follow
It is better to do an "order by" on a set independent of the current context and then do    
cross join.
           Additionally filter out Nulls before doing order by and reduce the size of set
 Use CInt instead of Val directly and not let AS decided the base data type it needs to  
 convert to under the hood. (Val does not throw an error if value is a string value whereas 
 CInt will throw an error  Type Mismatch.)
 Resort to BDESC rather than DESC. BDESC does a simple sort and not a hierarchical sort

Additionally I landed up in this article:- .http://sqlblog.com/blogs/mosha/archive/2008/10/22/optimizing-mdx-aggregation-functions.aspx which speaks about not using the NonEmpty Clause within the aggregate function. But in our case this does not apply as the order by is not an aggregate function.

To substantiate this and dig more into it, found some interesting stuff. Hrvoje helped me in this as he had the required volume of dataset.

So here were the readings

The fact table contains around 3M rows. The dimension contains around 249,607 members: [Dim1].[Attr1].[Attr1]. Dim1].[Attr2].[Attr2] contains around 160 members. [Dim1].[Attr1] is an hierarchy that has 7 levels.

a:- this is the time (mm:ss) for query created using set
b:- this is the time (mm:ss) for query created using ORDER directly in the cross join.

Query 1: 
a) Time 07:51
b) Time 07:51 

WITH
SET setL AS
ORDER
(
      [Dim1].[Attr1].[Attr1]
      ,Val([Dim1].[Attr1].CurrentMember.MEMBER_Key) -- Key is SK of int type
      ,DESC
)

SELECT
      [Measures].[M1] ON 0,
      NON EMPTY [Dim1].[Attr2].[Attr2] * setL ON 1
FROM
      [Cube1]



Query 2: 
a) Time 03:57  
b) Time 03:58 

WITH
SET setL AS
ORDER
(
      NonEmpty([Dim1].[Attr1].[Attr1], [Measures].[M1])
      , Val([Dim1].[Attr1].CurrentMember.MEMBER_Key)
      ,DESC
)

SELECT
      [Measures].[M1] ON 0,
      NON EMPTY [Dim1].[Attr2].[Attr2] * setL ON 1
FROM
[Cube1]


Query 3:
a) Time 01:06
b) Time 01:04

WITH
SET setL AS
ORDER
(
      NonEmpty([Dim1].[Attr1].[Attr1], [Measures].[M1])
      ,Int([Dim1].[Attr1].CurrentMember.MEMBER_Key)
      ,DESC
)

SELECT
      [Measures].[M1] ON 0,
      NON EMPTY [Dim1].[Attr2].[Attr2] * setL ON 1
FROM
[Cube1]



Query 4:
a) Time 00:46
b) Time 00:39

WITH
SET setL AS
ORDER
(
      NonEmpty([Dim1].[Attr1].[Attr1], [Measures].[M1])
      ,[Dim1].[Attr1].CurrentMember.MEMBER_Caption
      ,DESC
)

SELECT
      [Measures].[M1] ON 0,
      NON EMPTY [Dim1].[Attr2].[Attr2] * setL ON 1
FROM
[Cube1]



Conclusion
1. Using Val is a big culprit. Int is better than Val. But changing the source data type is the best.
2. It is better to use NonEmpty within ORDER().
3. Though there is no considerable difference between the creating a set versus using ORDER() directly in cross join, i think it is better practice to do so.
4. The observation is when both the attributes are belonging to same dimension. I assume that the behavior would be same when we use attributes of different dimension in a cross join
5. I could not do much analysis on BDESC versus DESC.


Note: This query difference may not be substantial if the dimension and measure data is small.

Thursday, February 17, 2011

mdx: query to override parent value with last child value

This query overrides the value of parent with the value of last child.

WITH  

MEMBER LevelOrdinal AS
       [Date].[Calendar].currentmember.level.ordinal
MEMBER LevelName AS
       [Date].[Calendar].currentmember.level.name
      
MEMBER [Measures].[ResellerOrderCountLastPeriod] AS
       (
              CLOSINGPERIOD(
                     [Date].[Calendar].[Month],
                     ANCESTOR(
                           [Date].[Calendar].CURRENTMEMBER,0
                     )
              )
              ,[Measures].[Reseller Order Count]
       ),
Back_Color =
       case when [Date].[Calendar].CURRENTMEMBER.level.ordinal=1 then RGB(0,255,255)
               when [Date].[Calendar].CURRENTMEMBER.level.ordinal=2 then RGB(125,255,255)
               when [Date].[Calendar].CURRENTMEMBER.level.ordinal=3 then RGB(200,255,255)
       else NULL
       end

             
SELECT
{
       LevelOrdinal,
       LevelName,
       [Measures].[Reseller Order Count],
       [Measures].[ResellerOrderCountLastPeriod]
} ON 0,
NONEMPTY
(
       EXCEPT(
              DESCENDANTS([Date].[Calendar].[Calendar Year],,SELF_AND_AFTER),
              [Date].[Calendar].[Date]
       )
       ,[Measures].[ResellerOrderCountLastPeriod]
)
ON 1
FROM
       [Adventure Works]
--WHERE
       --[Date].[Calendar Year].&[2001]
cell properties formatted_value, back_color

--I have added except to exclude the date level.



Result

Monday, January 17, 2011

MDX: Grand Total / Sub Total

One of the most common issues faced in mdx is grand total or sub total not coming properly when some arithmetic operations like measure1 [* or + or - or /]  measure2 is used


The various ways that one may think of while trying to resolve this are
1. scope
2. case statements
3. measure expression
4. named calculation in dsv / view
5. have the etl populate the data in required way
6. Visual Totals


the order listed is generally the preference taken to resolve the issue. the problem statement can further be  categorized by the usage of attribute hierarchy versus natural hierarchy in the row axis and whether changes to be done on the measure itself or to be done on the calculated measure.


let me build a geography dimension for this and revenue fact. The geography dimension will have a country-state-city hierarchy. The revenue will have revenue, tax percent, tax revenue measures.


CREATE TABLE [dbo].[Geography](
            [CityId] [int] IDENTITY(1,1) NOT NULL,
            [City] [varchar](50) NOT NULL,
            [State] [varchar](10) NULL,
            [StateCode] [varchar](50) NULL,
            [CountryCode] [varchar](50) NULL,
            [Country] [varchar](50) NULL,
 CONSTRAINT [PK_Country] PRIMARY KEY CLUSTERED
(
            [CityId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[Revenue](
            [id] [bigint] IDENTITY(1,1) NOT NULL,
            [CityId] [int] NULL,
            [Tax] [float] NULL,
            [Revenue] [money] NULL,
            [TaxRevenue] [money] NULL,
            [IsTaxConsidered] [bit] NULL,
 CONSTRAINT [PK_Revenue] PRIMARY KEY CLUSTERED
(
            [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]


In the cube, create a geography dimension and set the attribute relationship as follows as
City Id->City->State->Country, State->State Code, Country->Country Code and create a hierarchy Country->State->City. Hide the attributes City Id, Country Code and State Code.
Create a cube with Tax, Revenue and Tax Revenue measure with Aggregation type as SUM and relate the measure group and dimension with relationship type as Regular and key as City Id.


Now i would like to have a calculated measure having the formula: TaxRevenue= Revenue * Tax Revenue.


CREATE MEMBER CURRENTCUBE.[Measures].[cTaxRevenue] AS [Measures].[Revenue]*[Measures].[Tax],
VISIBLE = 1  ;
Result

Now if one see, clearly the grand total value is not proper. What it does multiplies the aggregated value. Expected value is 24. On the row axis, the city attribute hierarchy is used.
Scope

CREATE MEMBER CURRENTCUBE.[Measures].[cTaxRevenue] AS null,
VISIBLE = 1  ; 

SCOPE([Measures].[cTaxRevenue],[Geography].[City].members); --Includes All + other members
This = case when [Geography].[City].currentmember.level.ordinal>0 then
    [Measures].[Revenue]*[Measures].[Tax]
else
   sum([Geography].[City].[City].members, [Measures].[Revenue]*[Measures].[Tax]) --All member is not included
end
;
END SCOPE;
Result

if one includes All members, the result would be wrong, example: Geography.City.members were used. At this one applies filter by excluding Dallas, then still the grand total would be reading as 24 and not 16. Let us address this later. Now how about if one drops in Hierarchy on row axis.
Result




SCOPE([Measures].[cTaxRevenueH],[Geography].[GeoHier].members);
This =sum(
        Descendants([Geography].[GeoHier].CurrentMember,,LEAVES),
        [Measures].[Revenue]*[Measures].[Tax]);
END SCOPE;

This query works for both the attribute hierarchy as well as the geography hierarchy.





So all works well at this point of time. Now apply filter in by selecting City not in Bangalore. What you see now is that the total comes as 11 and not 8 though the data for Bangalore is filtered out.


To solve this problem, one could directly create a named calculation in Revenue fact in the DSV with expression as Revenue * Tax and then process the cube without any scope statements. Now applying filter should give you appropriate results. The filter is not honored by the scope statements. 


if one wants to make it generic, then we could change it to 

SCOPE([Measures].[cTaxRevenue]);
This =sum(
        EXISTING Descendants(Axis(0).Item(0).Item(0).Hierarchy.CurrentMember,,LEAVES),
        [Measures].[Revenue]*[Measures].[Tax]);
END SCOPE;


if this has to work with mdx in query analyzer, then Axis(0) need to be Axis(1). 
To work it both in browser and mdx query, change the query by adding a member like this

CREATE MEMBER CURRENTCUBE.[Measures].[AxisNo] as
 case
    when  IsError(Extract( Axis(0), Measures).Count) then 0
        when  IsError(Extract( Axis(1), Measures ).Count) then 1
        else -1
    end;

SCOPE([Measures].[cTaxRevenue]); 
This =sum(
       Descendants(Axis([Measures].[AxisNo]).Item(0).Item(0).Hierarchy.CurrentMember,,LEAVES),
        [Measures].[Revenue]*[Measures].[Tax]); 
END SCOPE;


Further to this if one wants to select certain cities in the Mdx, then apply Visual Totals for the totals to come properly as scope would not honor filters.



with member [Measures].[cTaxRevenueFilterApplied]  as
            case when  [Geography].[City].currentmember is [Geography].[City].[All] then
             sum(except(VisualTotals(axis(1)),[Geography].[City].[All]), [Measures].[Revenue]*[Measures].[Tax])
             else
              [Measures].[Revenue]*[Measures].[Tax]
             end
   
select {[Measures].[Tax],[Measures].[Revenue],[Measures].[cTaxRevenue], [Measures].[cTaxRevenueFilterApplied] } on 0,
{[Geography].[City].[All],[Geography].[City].&[Austin],[Geography].[City].&[Bangalore]} on 1 from [Cube]


Links
http://sqlblog.com/blogs/mosha/archive/2007/09/26/how-to-detect-subselect-inside-mdx-calculations-aka-multiselect-in-excel-2007.aspx



LinkWithin

Related Posts with Thumbnails