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

ssis lookup transfomation memory/performance issues

Suddenly today the package stopped responding. As usual I quote "It worked in the morning and this time it was connected to different server". The issue soon became a high priority as the system went on to throw a Low virtual memory error. The system had to be restarted couple of times and this was a pain.

Investigation resulted in narrowing down the root cause to be one of the lookups in the package.

The first thing that flashed to my mind was "Why are we not using Cache?". So I decided to create a Cache File using Cache Transform in a separate package. Looking into the Execution Result showed that the SSIS printed the following message


"Information: The buffer manager detected that system was low in virtual memory, but was unable to swap out any buffers to relieve memory pressure. 8 buffers were considered and 8 were locked. Either not enough memory
is available to the pipeline because not enough are installed, other processes were using it, or too many buffers are locked."

"[Cache Transform [28]] Information: The component "Cache Transform" (28) processed 1748563 rows in the cache. The processing time was 224.813 seconds. The cache used 14439633254 bytes of memory.
http://technet.microsoft.com/en-us/library/cc966529.aspx
"

I began to search for information on the maximum buffer size and found some information here http://technet.microsoft.com/en-us/library/cc966529.aspx. By default the max rows for data flow task in 10000 rows and default buffer size is 10485760 bytes (~10 MB). This can be increased to 104857600. (~100 MB).  


The package behaved the same even after increasing the size. But this time the OLEDB source and the cache transform task within the data flow task did show green status but however the data flow task never got completed. The Execution result flushed out the following message this time (Infact both the time but with some additional messages this time).
"[Cache Transform [28]] Information: The component "Cache Transform" (28) processed 1748563 rows in the cache. The processing time was 224.813 seconds. The cache used 14439633254 bytes of memory.
http://technet.microsoft.com/en-us/library/cc966529.aspx
"

So began to think why a simple query returning 2 million rows result in 14 GB of data. At this point I thought it is worth looking @ the query and other data type settings done by SSIS on the query. So here was the query


SELECT Number=REPLACE(REPLACE(LTRIM(REPLACE(rtrim(Number), '0', ' ')), ' ', '0'),'-',''), Cusip= ltrim(rtrim(cusip)),SponsorId, AccountId
FROM Account


The data type for the number and cusip column in the database was set as VARCHAR(20) and Id's as Int. Then I checked how the SSIS is setting the data length in the output and inputs properties (Click Advanced Properties) for OLEDB source.  To my horror found that the data length set was STRING 8000. So the SSIS was not able to decide the proper length when REPLACE or any other string manipulation function was used within the SQL query. So I changed the query to use CAST.


SELECT Number=CAST(REPLACE(REPLACE(LTRIM(REPLACE(rtrim(Number), '0', ' ')), ' ', '0'),'-','') as VARCHAR(20)), Cusip= CAST(ltrim(rtrim(cusip)) AS VARCHAR(20)),SponsorId, AccountId
FROM Account


The SSIS this time set the data length as STRING 20.  I ran the package again and it worked like a charm. The size of cache memory drastically reduced to 171359174 bytes (i.e. 160 MB). This was N times less than 14GB.  The package ran smoothly even without the idea of creating cache file.

"[Cache Transform [28]] Information: The component "Cache Transform" (28) processed 1748563 rows in the cache. The processing time was 0.531 seconds. The cache used 171359174 bytes of memory."

Wow. what a difference and what an impact.

We had two lookups using the same query and with the query change the package ran smoothly (Even without using the Cache). But just for curiosity, observed that each of these lookups used 160 MB of cache memory each (i.e. 320 MB) during execution.  Using the cache in the lookup made the package to use only 160 MB of cache memory as the package loaded the cache only once (the cache file itself was loaded separately in another package).

Post changes in query, the package ran even without having to increase the default buffer size (10MB) of data flow task. The lookup now took 160 MB of cache memory which was higher than the 100 MB max buffer size as mentioned in post which is an area of still for investigation.

Anyways for now, works great!

Summary:
·  Do have an eye on the query that you write and the data length set by SSIS on the columns of SSIS in lookups. Remember STRING is not same as VARCHAR in terms of memory allocation.
·  Do have an eye of the Max buffer size of Data Flow Task versus Total Cache memory consumed by data flow task
·  Do not get tricked by the amount of free space in the server. You still can run into the memory issue because of the limits set on individual tasks.
·  Save all the work before your try as sometime this experimentation results in system restart.

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



Thursday, January 13, 2011

MDX: Cummulative Sum

Some sample query for cummulative sum in MDX


WITH 
  
SET [Set Of Months] AS
    
Descendants
    (
      [Date].[Calendar].[Calendar Year].&[2003]
     ,[Date].[Calendar].[Month]
     ,SELF
    )
  
MEMBER [Measures].[Incremental Sum] AS
    
Sum
    (
      
Head
      (
        [Set Of Months]
       ,
Rank
        (
          [Date].[Calendar].
CurrentMember
         ,[Set Of Months]
        )
      )
     ,[Measures].[Internet Sales Amount]
    ) 
SELECT 
  {
    [Measures].[Internet Sales Amount]
   ,[Measures].[Incremental Sum]
  } ON 0
 ,[Set Of Months] ON 1
FROM [Adventure Works];

Friday, December 31, 2010

.CurrentOrdinal (Iteration in MDX)

Today I was trying to understand one of the queries found in MSDN for currentordinal and was keen to share my understanding. It was good to see how iteration is happening here and how the current co-ordinate values can be checked against other values.


WITH SET [PrdTies] AS 
Filter
 (
       Order
       (
           NonEmpty
           (
              {
                      [Product].[Product Categories].[Product].&[471], --4079
                      [Product].[Product Categories].[Product].&[433], --787
                      [Product].[Product Categories].[Product].&[434], --648
                      [Product].[Product Categories].[Product].&[475]  --787
              } 
              ,[Measures].[Reseller Order Quantity]
       )
      ,[Measures].[Reseller Order Quantity]
      ,BDESC
 ) AS OrdPrds,
 --NOT --commented for now.
 (
       OrdPrds.CurrentOrdinal < OrdPrds.Count 
       AND 
       [Measures].[Reseller Order Quantity]= 
       ( [Measures].[Reseller Order Quantity],OrdPrds.Item(OrdPrds.CurrentOrdinal)) 
       --Compares whether currentmember measure value = measure value for any other member
       --The current member value is compared with value of members after the current coordinate
        --As the member value is compared with members after current coordinate, we do not need to consider  
          the last member
       --Now this gets only 433
 )
OR 
(
        OrdPrds.CurrentOrdinal > 1 
       AND [Measures].[Reseller Order Quantity] = 
       ([Measures].[Reseller Order Quantity], OrdPrds.Item(OrdPrds.CurrentOrdinal-2))
      --but the value of 475 is same as 433. It does not come in first part. but this also qualifies
      --so compare with previous member by doing -2 (-1 compares with self)
)
 )
 
SELECT {[Measures].[Reseller Order Quantity]} ON 0, [PrdTies] ON 1
FROM [Adventure Works]
Additionally you might want to look at this

LinkWithin

Related Posts with Thumbnails