<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-13167615</id><updated>2023-11-05T03:12:39.228-06:00</updated><title type='text'>OLAP Monkey</title><subtitle type='html'>This site is dedicated to solutions I&#39;ve found to obscure and common problems when dealing with MDX and Microsoft&#39;s Analysis Services.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://olapmonkey.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default?alt=atom'/><link rel='alternate' type='text/html' href='http://olapmonkey.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Monty D. Xytopia</name><uri>http://www.blogger.com/profile/04884694631908189506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://www.rotten.com/library/cryptozoology/humanzee/humanzee.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>6</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-13167615.post-111893503626582986</id><published>2005-06-16T10:12:00.000-05:00</published><updated>2005-06-16T10:17:16.266-05:00</updated><title type='text'>How to aggregate across a specific time range?</title><content type='html'>In this posting we discuss how to aggregate data across a specific time range.  In the query below, we define a calculated member ([Time].[MyTime]) that uses the Aggregate function to aggregate data across the specific time range that is specified by the set &quot;[Time].[1997].[Q1].[1]:[Time].[1997].[Q2].[6]&quot;.  This set uses the &quot;:&quot; operator to specify a contiguous range of values in our time dimension.  We then use this newly created calculated member in our where close to force our values to be aggregated across this time period.&lt;br /&gt;&lt;br /&gt;Here&#39;s the query...&lt;br /&gt;&lt;br /&gt;with&lt;br /&gt;member [Time].[MyTime] as&lt;br /&gt;&#39;&lt;br /&gt;  Aggregate([Time].[1997].[Q1].[1]:[Time].[1997].[Q2].[6])&lt;br /&gt;&#39;&lt;br /&gt;select&lt;br /&gt;{&lt;br /&gt;  [Store].[USA].Children&lt;br /&gt;} on columns,&lt;br /&gt;{&lt;br /&gt;  [Measures].[Unit Sales]&lt;br /&gt;} on rows&lt;br /&gt;from Sales&lt;br /&gt;where&lt;br /&gt;(&lt;br /&gt;  [Time].[MyTime]&lt;br /&gt;)</content><link rel='replies' type='application/atom+xml' href='http://olapmonkey.blogspot.com/feeds/111893503626582986/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13167615&amp;postID=111893503626582986&amp;isPopup=true' title='409 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111893503626582986'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111893503626582986'/><link rel='alternate' type='text/html' href='http://olapmonkey.blogspot.com/2005/06/how-to-aggregate-across-specific-time.html' title='How to aggregate across a specific time range?'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04884694631908189506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>409</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13167615.post-111885065522599802</id><published>2005-06-15T10:47:00.000-05:00</published><updated>2005-06-15T10:53:04.583-05:00</updated><title type='text'>Can&#39;t Teach An Old Dog New Tricks</title><content type='html'>While you can&#39;t teach an old dog new tricks, it seems this old adage is not necessarily applicable to the humanzee. I learned a few things from the blow posting.&lt;br /&gt;&lt;br /&gt;I consider it recommended viewing for all humanzees. Canines on the other hand...dont&#39; waste your time.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://support.microsoft.com/default.aspx?kbid=886991&quot;&gt;TechNet Support WebCast: Common MDX mistakes and solutions in Microsoft SQL Server 2000 Analysis Services&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;- Monty</content><link rel='replies' type='application/atom+xml' href='http://olapmonkey.blogspot.com/feeds/111885065522599802/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13167615&amp;postID=111885065522599802&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111885065522599802'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111885065522599802'/><link rel='alternate' type='text/html' href='http://olapmonkey.blogspot.com/2005/06/cant-teach-old-dog-new-tricks.html' title='Can&#39;t Teach An Old Dog New Tricks'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04884694631908189506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13167615.post-111833265966696989</id><published>2005-06-09T10:30:00.000-05:00</published><updated>2005-06-09T10:59:55.386-05:00</updated><title type='text'>How to default you time dimension to show the most recent time period?</title><content type='html'>A question I often hear asked is &quot;How do I get my time dimension to default to the current time period&quot;?  The first problem with the question is what is meant by &quot;current time period&quot;.  In my experience, 8 of 10 times when folks say &quot;current time period&quot; that means the latest member of time dimension at it&#39;s leaf level that happens to have data in the fact table.  &lt;br /&gt;&lt;br /&gt;For instance, if you use Foodmart 2000 it has time periods for all the months in 1997 and 1998, but the data is only associated with 1997 for the fact table in the Sales cube.  So if by &quot;period&quot; one happens to mean &quot;month&quot;, then the most recent period with data in the fact table is [Time].[1997].[Q4].[12].&lt;br /&gt;&lt;br /&gt;Being that MDX is unnecessarily cryptic...such a simple request becomes masked in the minutia of the language itself.  On most of my cubes...I like to setup a default time member on my time dimension. I do so with a statement that looks like this...&lt;br /&gt;&lt;br /&gt;Tail(NonEmptyCrossJoin([Time].[Month].Members)).Item(0).Item(0)&lt;br /&gt;&lt;br /&gt;You can adjust the &quot;[Time].[Month]&quot; part of the expression to reflect whatever level of your time dimension you want to be used as the default.  The NonEmptyCrossJoin part of the expression...which actually is cross joining to nothing...is the part of the expression that limits the list of members to only those that have data in the fact table.  The Tail part of the expression is retrieving the last item in the list.  Then in classic MDX cryptology we need to use the &quot;.Item(0).Item(0)&quot; to morph this last item in the set from a set into the member it represents.</content><link rel='replies' type='application/atom+xml' href='http://olapmonkey.blogspot.com/feeds/111833265966696989/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13167615&amp;postID=111833265966696989&amp;isPopup=true' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111833265966696989'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111833265966696989'/><link rel='alternate' type='text/html' href='http://olapmonkey.blogspot.com/2005/06/how-to-default-you-time-dimension-to.html' title='How to default you time dimension to show the most recent time period?'/><author><name>Anonymous</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/blank.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13167615.post-111721352133124975</id><published>2005-05-27T11:47:00.000-05:00</published><updated>2005-05-27T12:05:21.680-05:00</updated><title type='text'>How do you find the product of a series of numbers using MDX?</title><content type='html'>This posting shows how to use a recursive calculated member to find the product of a series of numbers.  In the particular case illustrated, I&#39;m using the Foodmart 2000 database (as I always do so that the concept is transportable to any reader).  &lt;br /&gt;&lt;br /&gt;Unfortunately, I didn&#39;t see any good reason to do this in Foodmart anywhere (nor did I want to take the time to construct something :) ) so this example is designed to show how to perform the technique...not necessarily highlight it in a real world example where it would be sensible.&lt;br /&gt;&lt;br /&gt;I&#39;ve heard of others that have had this need with a series of rates of returns of stock on a daily basis.  To determine the MTD rate of returns, apparently the series of numbers need to be multiplied.&lt;br /&gt;&lt;br /&gt;Here&#39;s what the MDX might look like...&lt;br /&gt;&lt;br /&gt;With member [Measures].[SalesAvgMultiplied] as&lt;br /&gt;&#39;&lt;br /&gt;iif(&lt;br /&gt; IsLeaf([Time].CurrentMember)&lt;br /&gt; ,iif(&lt;br /&gt;   [Time].CurrentMember IS [Time].CurrentMember.Parent.FirstChild&lt;br /&gt;   ,[Measures].[Sales Average]&lt;br /&gt;   ,([Measures].[SalesAvgMultiplied], [Time].PrevMember) * [Measures].[Sales Average]&lt;br /&gt;  )&lt;br /&gt; ,[Time].CurrentMember.LastChild&lt;br /&gt;)&lt;br /&gt;&#39;&lt;br /&gt;select &lt;br /&gt;{&lt;br /&gt;  [Measures].[Sales Average],[Measures].[SalesAvgMultiplied] &lt;br /&gt;} on columns, &lt;br /&gt;{&lt;br /&gt;  [Time].Members&lt;br /&gt;} on rows&lt;br /&gt;from Sales&lt;br /&gt;&lt;br /&gt;The calculated member first uses the standard non-additive rollup technique to move the last child up to the time aggregate levels (why did I do this?...just felt like it :) ).  Next it uses a standard recursion technique by implementing a stop point for the recursion in an IIF function (the [Time].CurrentMember IS [Time].CurrentMember.Parent.FirstChild).  This stops the recursion when your leaf level member reaches it&#39;s first sibling.  If you aren&#39;t at the first sibling, however, it then multiplies itself from the previous period against the value in the current period you are multiplying through time.</content><link rel='replies' type='application/atom+xml' href='http://olapmonkey.blogspot.com/feeds/111721352133124975/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13167615&amp;postID=111721352133124975&amp;isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111721352133124975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111721352133124975'/><link rel='alternate' type='text/html' href='http://olapmonkey.blogspot.com/2005/05/how-do-you-find-product-of-series-of.html' title='How do you find the product of a series of numbers using MDX?'/><author><name>Anonymous</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/blank.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13167615.post-111715767249945762</id><published>2005-05-26T20:15:00.000-05:00</published><updated>2005-05-26T20:34:32.503-05:00</updated><title type='text'>Recursive Calculated Member Against Member Property</title><content type='html'>This example shows how to utilize a recursive calculated member to sum a numeric member property up to all it&#39;s parent levels. In this specific case, the Foodmart Store dimension has a &quot;Store Sqft&quot; member property which is numeric. That member property only lives at the leaf level (the Store Name level) of the dimension. In this example we create a calculated member that interrogates whether or not we are at the leaf level of the dimension...if we are, it will output the value of the &quot;Store Sqft&quot; member property...if we are not at the leaf level, then the calculated member will sum the value of itself (i.e. the recursive part) for all it&#39;s children.&lt;br /&gt;&lt;br /&gt;The MDX example is as follows:&lt;br /&gt;&lt;br /&gt;with&lt;br /&gt;member [Measures].[StoreSqFt] as&lt;br /&gt;&#39;&lt;br /&gt;  iif(&lt;br /&gt;    IsLeaf([Store].CurrentMember)&lt;br /&gt;    ,val([Store].CurrentMember.Properties(&quot;Store Sqft&quot;))&lt;br /&gt;    ,Sum([Store].CurrentMember.Children,[Measures].[StoreSqFt])&lt;br /&gt;)&lt;br /&gt;&#39;&lt;br /&gt;member [Measures].[StoreSalesPerSqFt] as&lt;br /&gt;&#39;&lt;br /&gt;  iif(&lt;br /&gt;    [Measures].[StoreSqFt] = 0&lt;br /&gt;    , NULL&lt;br /&gt;    , [Measures].[Store Sales] / [Measures].[StoreSqFt]&lt;br /&gt;)&lt;br /&gt;&#39;&lt;br /&gt;select&lt;br /&gt;{&lt;br /&gt;  [Measures].[Store Sales]&lt;br /&gt;  ,[Measures].[StoreSqFt]&lt;br /&gt;  ,[Measures].[StoreSalesPerSqFt]&lt;br /&gt;} on columns,&lt;br /&gt;{&lt;br /&gt;  Order(&lt;br /&gt;    Descendants([Store].[All Stores].[USA])&lt;br /&gt;    ,[Measures].[StoreSalesPerSqFt]&lt;br /&gt;    ,BDESC&lt;br /&gt;  )&lt;br /&gt;} on rows&lt;br /&gt;from Sales&lt;br /&gt;&lt;br /&gt;This query outputs the total Store Sales, the total square footage available in stores, and the store sales per sqft for each member of the store dimension regardless of level.   It sorts the output by store sales per sqft breaking the hierarchy so we can quickly see which stores, cities, and states are using their space most efficiently to generate sales.</content><link rel='replies' type='application/atom+xml' href='http://olapmonkey.blogspot.com/feeds/111715767249945762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13167615&amp;postID=111715767249945762&amp;isPopup=true' title='96 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111715767249945762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111715767249945762'/><link rel='alternate' type='text/html' href='http://olapmonkey.blogspot.com/2005/05/recursive-calculated-member-against.html' title='Recursive Calculated Member Against Member Property'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/04884694631908189506</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>96</thr:total></entry><entry><id>tag:blogger.com,1999:blog-13167615.post-111703899620931207</id><published>2005-05-25T11:10:00.000-05:00</published><updated>2005-05-25T11:36:36.213-05:00</updated><title type='text'>Conditionally taking an action based upon member existence</title><content type='html'>Sometimes members might come and go for numerous reasons.  Maybe one user has access to a member and the next does not...or maybe the relational data behind your member comes and goes (like time periods) for some reason.  In any event, it&#39;s sometimes useful in MDX to be able to check whether a member is a valid member prior to performing some operation that uses that member.&lt;br /&gt;&lt;br /&gt;In order to do this...you can use the IsError() function combined with the StrToMember() function combined with concatenation of a null string within the IIF() function.  Clear as mud right?&lt;br /&gt;&lt;br /&gt;At first glance...one might think that a query like this would do the job...&lt;br /&gt;&lt;br /&gt;with member [Measures].[Test] as&lt;br /&gt;&#39;&lt;br /&gt;  iif( &lt;br /&gt;    IsError(StrToMember(&quot;[Gender].[All Gender].[Z]&quot;)) &lt;br /&gt;    ,&quot;Not Found&quot; &lt;br /&gt;    ,&quot;Found&quot;&lt;br /&gt;  )&lt;br /&gt;&#39;select&lt;br /&gt;{&lt;br /&gt;  [Measures].[Test]&lt;br /&gt;} on columns,&lt;br /&gt;{&lt;br /&gt;  [Education Level].Members&lt;br /&gt;} on rows&lt;br /&gt;from Sales&lt;br /&gt;&lt;br /&gt;Problem is the MDX parser is smarter than you average humanzee and in its attempt at genius will error out the above statement prior to execution delcaring that it cannot find dimension member [Gender].[All Gender].[Z].  Well since there is no gender Z in our cube that is of course correct...however...the purpose of the calculated member above is to account for this at execution time...but the ingenious MDX parser got in our way.  The key to getting around it&#39;s genius is to use some good old fashioned humanzee stupidity.  You simply concatenate a null string onto the string used in the StrToMember function.  This apparently tells the MDX parser that it&#39;s dealing with some kind of dynamic string and thus should just go ahead and do what it was asked to instead of trying to be smarter than us.  So the new query would look like this...&lt;br /&gt;&lt;br /&gt;with member [Measures].[Test] as&lt;br /&gt;&#39;&lt;br /&gt;  iif( &lt;br /&gt;    IsError(StrToMember(&quot;[Gender].[All Gender].[Z]&quot; + &quot;&quot;)) &lt;br /&gt;    ,&quot;Not Found&quot; &lt;br /&gt;    ,&quot;Found&quot;&lt;br /&gt;  )&lt;br /&gt;&#39;select&lt;br /&gt;{&lt;br /&gt;  [Measures].[Test]&lt;br /&gt;} on columns,&lt;br /&gt;{&lt;br /&gt;  [Education Level].Members&lt;br /&gt;} on rows&lt;br /&gt;from Sales&lt;br /&gt;&lt;br /&gt;This query instead of erroring out at parse time, executes as intended and returns the result of &quot;Not Found&quot; for the member [Gender].[All Gender].[Z].  If you change [Gender].[All Gender].[Z] to [Gender].[All Gender].[M] the query will return &quot;Found&quot;.</content><link rel='replies' type='application/atom+xml' href='http://olapmonkey.blogspot.com/feeds/111703899620931207/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=13167615&amp;postID=111703899620931207&amp;isPopup=true' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111703899620931207'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/13167615/posts/default/111703899620931207'/><link rel='alternate' type='text/html' href='http://olapmonkey.blogspot.com/2005/05/conditionally-taking-action-based-upon.html' title='Conditionally taking an action based upon member existence'/><author><name>Anonymous</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/blank.gif'/></author><thr:total>16</thr:total></entry></feed>