<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Purple Frog Systems &#187; Cube</title>
	<atom:link href="http://www.purplefrogsystems.com/blog/tag/cube/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.purplefrogsystems.com/blog</link>
	<description>Business Intelligence Consultancy</description>
	<lastBuildDate>Mon, 23 Jan 2012 09:11:10 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Video: Automating SSAS OLAP Cube documentation</title>
		<link>http://www.purplefrogsystems.com/blog/2011/05/video-automating-ssas-olap-cube-documentation/</link>
		<comments>http://www.purplefrogsystems.com/blog/2011/05/video-automating-ssas-olap-cube-documentation/#comments</comments>
		<pubDate>Thu, 26 May 2011 12:48:04 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Business Intelligence]]></category>
		<category><![CDATA[Reporting Services]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[Documentation]]></category>
		<category><![CDATA[SQLBits]]></category>
		<category><![CDATA[SSAS]]></category>
		<category><![CDATA[Star Schema]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=394</guid>
		<description><![CDATA[Automating OLAP cube documentation &#8211; SQLBits presentation For anyone that missed my presentation at SQLBits 8 in April, the video is now available here. In this 1 hour session I present a method of automating the creation of documentation for SSAS OLAP cubes by using DMVs (dynamic management views) and spatial data, querying the metadata [...]]]></description>
			<content:encoded><![CDATA[<h1>Automating OLAP cube documentation &#8211; SQLBits presentation</h1>
<p>For anyone that missed my presentation at SQLBits 8 in April, <a href="http://sqlbits.com/Sessions/Event8/Automating_SSAS_cube_documentation_using_SSRS_DMV_and_Spatial_Data">the video is now available here</a>.</p>
<p><br/></p>
<p>In this 1 hour session I present a method of automating the creation of documentation for SSAS OLAP cubes by using DMVs (dynamic management views) and spatial data, querying the metadata of the cube in realtime.</p>
<p>The results include the BUS matrix, star schemas, attribute lists, hierarchies etc. and are all presented in SSRS.</p>
<p><br/></p>
<p><a href="http://sqlbits.com/Sessions/Event8/Automating_SSAS_cube_documentation_using_SSRS_DMV_and_Spatial_Data"><img src="http://www.purplefrogsystems.com/blog/wp-content/uploads/2011/05/SQLBits8Video_m-300x164.jpg" alt="" title="SQLBits8Video_m" width="300" height="164" class="alignleft size-medium wp-image-397" /></a>The blog posts to go with this are here:</p>
<ul>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/">Part 1</a></li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/">Part 2</a></li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/">Part 3</a></li>
</ul>
<p><br/></p>
<p>You can view the slide deck <a href="http://bit.ly/cubedocslides">here</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2011/05/video-automating-ssas-olap-cube-documentation/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Debug MDX queries using Drillthrough in SSMS</title>
		<link>http://www.purplefrogsystems.com/blog/2011/02/debug-mdx-queries-using-drillthrough-in-ssms/</link>
		<comments>http://www.purplefrogsystems.com/blog/2011/02/debug-mdx-queries-using-drillthrough-in-ssms/#comments</comments>
		<pubDate>Wed, 02 Feb 2011 18:30:31 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[Drillthrough]]></category>
		<category><![CDATA[Excel]]></category>
		<category><![CDATA[MDX]]></category>
		<category><![CDATA[OLAP]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=311</guid>
		<description><![CDATA[One of the great features of using Excel to browse an SSAS OLAP cube is the drillthrough ability. If you double click on any cell of an OLAP pivot table, Excel will create a new worksheet containing the top 1000 fact records that went to make up the figure in the selected cell. N.B. The [...]]]></description>
			<content:encoded><![CDATA[<p>One of the great features of using Excel to browse an SSAS OLAP cube is the drillthrough ability. If you double click on any cell of an OLAP pivot table, Excel will create a new worksheet containing the top 1000 fact records that went to make up the figure in the selected cell.</p>
<p><img src="http://www.purplefrogsystems.com/blog/wp-content/uploads/2011/02/MDXDrillthrough0-1024x598.png" alt="" title="MDXDrillthrough0" width="600" height="350" class="aligncenter size-large wp-image-312" /></p>
<p>N.B. <a href="/blog/2010/03/excel-cube-pivot-drillthrough-limited-to-1000-rows/">The limit of 1000 rows can be altered</a>, as per one of my previous blog posts <a href="">here</a>.</p>
<p><br/></p>
<p>This feature is pretty well known, but not many folk realise how easy it is to reproduce this in SQL Server Management Studio (SSMS). All you need to do is prefix your query with DRILLTHROUGH.</p>
<p>i.e. Assuming an MDX query of</p>
<p class="code">SELECT [Measures].[Internet Sales Amount] ON 0<br />
FROM [Adventure Works]<br />
WHERE [Date].[January 1, 2004]
</p>
<p>Which returns the following results&#8230;</p>
<p><img src="http://www.purplefrogsystems.com/blog/wp-content/uploads/2011/02/MDXDrillthrough1.png" alt="" title="MDXDrillthrough1" width="406" height="153" class="aligncenter size-full wp-image-313" /></p>
<p>A query of</p>
<p class="code">DRILLTHROUGH<br />
SELECT [Measures].[Internet Sales Amount] ON 0<br />
FROM [Adventure Works]<br />
WHERE [Date].[January 1, 2004]
</p>
<p>Returns the records contributing to the total figure. Great for diagnosing problems with an MDX query.</p>
<p><img src="http://www.purplefrogsystems.com/blog/wp-content/uploads/2011/02/MDXDrillthrough2.png" alt="" title="MDXDrillthrough2" width="609" height="335" class="aligncenter size-full wp-image-314" /></p>
<p>By default, only the first 10,000 rows are returned, but you can override this using MAXROWS</p>
<p class="code">DRILLTHROUGH MAXROWS 500<br />
SELECT [Measures].[Internet Sales Amount] ON 0<br />
FROM [Adventure Works]<br />
WHERE [Date].[January 1, 2004]
</p>
<p>The columns that are returned are those defined in the Actions tab of the Cube Designer in BIDS (The Business Intelligence Development Studio).</p>
<p><img src="http://www.purplefrogsystems.com/blog/wp-content/uploads/2011/02/MDXDrillthrough31.png" alt="" title="MDXDrillthrough3" width="597" height="298" class="aligncenter size-full wp-image-321" /></p>
<p>If no action is defined, then the fact measures will be returned along with the keys that link to each relevant dimension, which tend not to be that helpful.</p>
<p><br/>
<p>You can override the returned columns by using the RETURN clause</p>
<pre>
<p class="code">DRILLTHROUGH
SELECT [Measures].[Internet Sales Amount] ON 0
FROM [Adventure Works]
WHERE [Date].[January 1, 2004]
RETURN
   [$Internet Sales Order Details].[Internet Sales Order]
  ,[$Sales Territory].[Sales Territory Region]
  ,NAME([$Product].[Product])
  ,KEY([$Product].[Product])
  ,UNIQUENAME([$Product].[Product])
  ,[Internet Sales].[Internet Sales Amount]
  ,[Internet Sales].[Internet Order Quantity]

</pre>
<p>
<img src="http://www.purplefrogsystems.com/blog/wp-content/uploads/2011/02/MDXDrillthrough41.png" alt="" title="MDXDrillthrough4" width="596" height="401" class="aligncenter size-full wp-image-320" /><br />
<br/></p>
<p>Note that there are some restrictions on what you can drill through</p>
<ul>
<li>You can&#8217;t drill through an expression/calculation, only a raw measure
<li>The MDX query needs to return a single cell (otherwise the cube does not know which one to drill through)
<li>The data returned will be at the lowest granularity of the cube&#8217;s fact table
</ul>
</p>
<p>To explain the last point further, the cube does not return the raw data from the underlying data warehouse, but a summary of the facts grouped by unique combination of the relevant dimensions. i.e. if a warehouse table containing individual sales (by date, product, customer &#038; store) is brought into a cube as a fact table that only has relationships with the date and product dimensions, then the cube drill through will return unique combinations of date and product, summarising sales for each combination. Extra granularity which the warehouse may contain (customer and store) will not be available.</p>
<p>Note that if you specify the RETURN columns, the rows are still returned at the lowest level of the fact table granularity, even if not all of the dimensions are brought out as columns. This may result in returning multiple identical records. Don&#8217;t worry, these will be distinct facts, just differentiated by a dimension/attribute that isn&#8217;t being returned.</p>
<p align="right">You can find out more on <a href="http://technet.microsoft.com/en-us/library/ms145964.aspx" target="_blank">TechNet here</a></p>
<p><br/></p>
<p>Frog-Blog Out</p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2011/02/debug-mdx-queries-using-drillthrough-in-ssms/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>How to add calculations in SSAS cubes</title>
		<link>http://www.purplefrogsystems.com/blog/2011/01/how-to-add-calculations-in-ssas-cubes/</link>
		<comments>http://www.purplefrogsystems.com/blog/2011/01/how-to-add-calculations-in-ssas-cubes/#comments</comments>
		<pubDate>Sun, 16 Jan 2011 14:33:30 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Business Intelligence]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Calculated Member]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[DSV]]></category>
		<category><![CDATA[SSAS]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=300</guid>
		<description><![CDATA[One of the most useful aspects of a Business Intelligence system is the ability to add calculations to create new measures. This centralises the logic of the calculation into a single place, ensuring consistency and standardisation across the user base. By way of example, a simple calculation for profit (Income – Expenditure) wouldn’t be provided [...]]]></description>
			<content:encoded><![CDATA[<p>One of the most useful aspects of a Business Intelligence system is the ability to add calculations to create new measures. This centralises the logic of the calculation into a single place, ensuring consistency and standardisation across the user base.</p>
<p>By way of example, a simple calculation for profit (Income – Expenditure) wouldn’t be provided by the source database and historically would be implemented in each and every report. In a data warehouse and/or cube we can create the calculation in a single place for everyone to use.</p>
<p>This post highlights some of methods of doing this, each with their respective pros and cons. </p>
<h2>Calculated Members in SSAS Cube</h2>
<p><img src="/images/blog/SSASCalculatedMember.png" align="center"><br />
SSAS provides a ‘Calculations’ tab in the cube designer which allows you to create new measures using MDX. You can use any combination of existing measures and dimension attributes, along with the plethora of MDX functions available to create highly complex calculations.<br />
<strong>Pros:</strong></p>
<ul>
<li>Very complex calculations can be created using all available MDX functions</li>
<li>No changes are required to the structure of the data warehouse</li>
<li>Changes to the calculation will apply to every record, historic and new </li>
<li>The results are not stored in the warehouse or cube, so no extra space is required</li>
<li>New calculations can be added without having to deploy or reprocess the cube</li>
<li>Calculations can be scoped to any level of aggregation and granularity. Different calculations can even be used for different scopes</li>
<li>Calculations can easily combine measures from different measure groups</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>The calculation will not make use of SSAS cube aggregations, reducing performance</li>
<li>SSAS drill through actions will not work</li>
<li>The calculation results are not available in the data warehouse, only the cube</li>
</ul>
<h2>SQL Calculations in the Data Source View</h2>
<p><img src="/images/blog/SSASDSVQuery.png" align="center"><br />
There’s a layer in-between the data warehouse and the cube called the data source view (DSV). This presents the relevant tables in the warehouse to the cube, and can be used to enhance the underlying data with calculations. This can either be the dsv layer within the cube project, or I prefer to create SQL Server views to encapsulate the logic.<br />
<strong>Pros:</strong></p>
<ul>
<li>No changes are required to the table structure of the data warehouse</li>
<li>Calculations use SQL not MDX, reducing the complexity</li>
<li>Changes to the calculation will apply to every record, historic and new </li>
<li>The calculation will make full use of SSAS cube aggregations</li>
<li>SSAS drill through actions will work</li>
<li>The results are not stored in the warehouse, so the size of the database does not increase</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>The cube must be redeployed and reprocessed before the new measure is available</li>
<li>The results of the calculation must be valid at the granularity of the fact table</li>
<li>The calculation results are not available in the data warehouse, only the cube</li>
</ul>
<h2>Calculate in the ETL process</h2>
<p><img src="/images/blog/ETLCalculation.png" align="center"><br />
Whilst bringing in data from the source data systems, it sometimes makes sense to perform calculations on the data at that point, and store the results in the warehouse.<br />
<strong>Pros:</strong></p>
<ul>
<li>The results of the calculation will be available when querying the warehouse as well as the cube</li>
<li>In the ETL pipeline you can import other data sources (using lookups etc.) to utilise other data in the calculation</li>
<li>If the calculation uses time based data, or data valid at a specific time (i.e. share price) then by performing the calculation in the ETL, the correct time based data is used, without having to store the full history of the underlying source data</li>
<li>The calculation will make full use of SSAS cube aggregations</li>
<li>SSAS drill through actions will work</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>You have to be able to alter the structure of the data warehouse, which isn’t always an option.</li>
<li>The results are stored in the warehouse, increasing the size of the database</li>
<li>The results of the calculation must be valid at the granularity of the fact table</li>
<li>If the calculation logic changes, all existing records must be updated</li>
</ul>
<p><br/></p>
<h2>In Conclusion</h2>
<p>If the calculation is valid for a single record, and it would be of benefit to have access to the results in the warehouse, then perform the calculation in the ETL pipeline and store teh results in the warehouse.</p>
<p>If the calculation is valid for a single record, and it would not be of benefit to have the results in the warehouse, then calculate it in the data source view.</p>
<p>If the calculation is too complex for SQL, requiring MDX functions, then create an MDX calculated measure in the cube.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2011/01/how-to-add-calculations-in-ssas-cubes/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>OLAP Cube Documentation in SSRS part 3</title>
		<link>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/</link>
		<comments>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/#comments</comments>
		<pubDate>Wed, 29 Sep 2010 18:18:40 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Business Intelligence]]></category>
		<category><![CDATA[Reporting Services]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[BUS Matrix]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[Documentation]]></category>
		<category><![CDATA[Kimball]]></category>
		<category><![CDATA[Map]]></category>
		<category><![CDATA[OLAP]]></category>
		<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SSAS]]></category>
		<category><![CDATA[SSRS]]></category>
		<category><![CDATA[Star Schema]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=208</guid>
		<description><![CDATA[This is the 3rd and final post in this series of blog posts, showing how you can use SQL Server Reporting Services (SSRS), DMVs and spatial data to generate real time automated user guide documentation for your Analysis Services (SSAS) OLAP cube. Part 1 &#8211; Creating the DMV stored procs Part 2 &#8211; Create the [...]]]></description>
			<content:encoded><![CDATA[<p>This is the 3rd and final post in this series of blog posts, showing how you can use SQL Server Reporting Services (SSRS), DMVs and spatial data to generate real time automated user guide documentation for your Analysis Services (SSAS) OLAP cube.</p>
<ul>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/">Part 1 &#8211; Creating the DMV stored procs</a></li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/">Part 2 &#8211; Create the SSRS reports</a></li>
<li>Part 3 &#8211; Use spatial data and maps to create a star schema view</li>
<li><a onclick="pageTracker._trackPageview('/downloads/CubeDoc.zip'); " href="/download/blog/CubeDoc.zip" target="_self">Download Source Code</a></li>
</ul>
<p><strong>UPDATE</strong>: I presented a 1 hour session at SQLBits 8 covering this work, <a href="http://sqlbits.com/Sessions/Event8/Automating_SSAS_cube_documentation_using_SSRS_DMV_and_Spatial_Data" target="_blank">you can watch the video here</a>.</p>
<p>In this post, I&#8217;m going to enhance the measure group report to include a visualisation of the star schema. To do this I&#8217;ll be enhancing one of the stored procedures to utilise SQL Server 2008&#8242;s new spatial data types combined with SSRS 2008 R2&#8242;s new map functionality.</p>
<p style="text-align: center;"><img class="aligncenter" style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_11.png" alt="" /></p>
<p>To do this we&#8217;ll update the dbo.upCubeDocDimensionsForMeasureGroup stored proc so that it returns a SQL geometry polygon for each row, in the right place around the circumference of the star. There&#8217;s a little math in this, but nothing more than a bit of trigonometry.</p>
<p>First the theory. We have an arbitrary number of dimensions that we need to place in a circle around a central point (the measure group). If we have 6 dimensions, then we need to divide the whole circle (360 degrees) by 6 (=60 degrees each) to get the angle of each around the hypothetical axis.</p>
<p style="text-align: center;"><img class="aligncenter" style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_12.png" alt="" /></p>
<p>Therefore the first dimension needs to be at 60, the second at 120, the third at 180 etc, with the 6th at 360, completing the full circle.<br />
Obviously the angle needs to vary depending on the number of dimensions in the query, so we need to calculate it within the stored proc. To do this I&#8217;m using common table expressions (CTE) to perform further calculations on the basic query.</p>
<p>We wrap the original proc query into a CTE and call it BaseData. We also add an extra field called Seq, which uniquely identifies each row, we&#8217;ll use this later to enable us to rank the dimensions.</p>
<pre>
;WITH BaseData AS
(
    SELECT
          mgd.*
        , d.[DESCRIPTION]
        , REPLACE(REPLACE(CAST(mgd.[DIMENSION_UNIQUE_NAME]
              AS VARCHAR(255))
              ,'[',''),']','') AS DimensionCaption
        , REPLACE(REPLACE(CAST(mgd.[MEASUREGROUP_NAME]
              AS VARCHAR(255))
              ,'[',''),']','') AS MeasureGroupCaption
    FROM OPENQUERY(CubeLinkedServer, 'SELECT
                [CATALOG_NAME]
                   +[CUBE_NAME]
                   +[MEASUREGROUP_NAME]
                   +[DIMENSION_UNIQUE_NAME] AS Seq
                , [CATALOG_NAME]
                , [CUBE_NAME]
                , [MEASUREGROUP_NAME]
                , [MEASUREGROUP_CARDINALITY]
                , [DIMENSION_UNIQUE_NAME]
                , [DIMENSION_CARDINALITY]
                , [DIMENSION_IS_VISIBLE]
                , [DIMENSION_IS_FACT_DIMENSION]
                , [DIMENSION_GRANULARITY]
            FROM $SYSTEM.MDSCHEMA_MEASUREGROUP_DIMENSIONS
            WHERE [DIMENSION_IS_VISIBLE]') mgd
        INNER JOIN OPENQUERY(CubeLinkedServer, 'SELECT
                [CATALOG_NAME]
                ,[CUBE_NAME]
                ,[DIMENSION_UNIQUE_NAME]
                ,[DESCRIPTION]
            FROM $SYSTEM.MDSCHEMA_DIMENSIONS
            WHERE [DIMENSION_IS_VISIBLE]') d
                ON  CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))
                  = CAST(d.[CATALOG_NAME] AS VARCHAR(255))
                AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))
                  = CAST(d.[CUBE_NAME] AS VARCHAR(255))
                AND CAST(mgd.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
                  = CAST(d.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
     WHERE  CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))     = @Catalog
       AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))         = @Cube
       AND CAST(mgd.[MEASUREGROUP_NAME] AS VARCHAR(255)) = @MeasureGroup
)
</pre>
<p>We&#8217;ll then add a new CTE which calculated the number of records returned by the previous query.</p>
<pre>
,TotCount AS
(
    SELECT COUNT(*) AS RecCount FROM BaseData
)
</pre>
<p>Next we cross join TotCount with the base data, so that every row has the extra RecCount field. We then rank each record, providing each with a unique number from 1 to n.</p>
<pre>
, RecCount AS
(
    SELECT RANK() OVER (ORDER BY CAST(Seq AS VARCHAR(255))) AS RecID
        , RecCount
        , BaseData.*
    FROM
        BaseData CROSS JOIN TotCount
)
</pre>
<p>Each record now contains its row number, as well as the total number of rows, so it&#8217;s easy to calculate its position around the circle (rank/n * 360). Now we have that, calculating the x and y coordinates of each dimension is simply a case of applying Sine and Cosine. Note that the SQL SIN and COS functions expect angles to be provided in radians not degrees, so we have to use the RADIANS function to convert it for us. I&#8217;m also multiplying the result by 1000 to scale the numbers up from -1 to +1 to -1000 to +1000, which makes our life easier later on.</p>
<pre>
, Angles AS
(
    SELECT
        *
        , SIN(RADIANS((CAST(RecID AS FLOAT)
            /CAST(RecCount AS FLOAT))
            * 360)) * 1000 AS x
        , COS(RADIANS((CAST(RecID AS FLOAT)
            /CAST(RecCount AS FLOAT))
            * 360)) * 1000 AS y
    FROM RecCount
)
</pre>
<p>We can now use the x and y coordinates to create a point indicating the position of each dimension, using the code below.</p>
<pre>
geometry::STGeomFromText('POINT('
   + CAST(y AS VARCHAR(20))
   + ' '
   + CAST(x AS VARCHAR(20))
   + ')',4326) AS Posn
</pre>
<p>This is a good start, but we want a polygon box, not a single point. We can use a similar geometry function to create a polygon around our point.</p>
<pre>
geometry::STPolyFromText('POLYGON ((' +
   CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
 	  + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
   CAST((y*@Stretch)-@BoxSize AS VARCHAR(20)) + ' '
	  + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
   CAST((y*@Stretch)-@BoxSize AS VARCHAR(20)) + ' '
	  + CAST(x-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
   CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
      + CAST(x-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
   CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
	  + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + '
   ))',0) AS Box
</pre>
<p>You&#8217;ll notice that I&#8217;m multiplying the y axis by a @Stretch variable. This is to allow us to squash or squeeze the resulting star to make it look better in the report. I&#8217;m also using a @BoxSize variable which we can use to change the relative size of the boxes. It&#8217;s for this reason why I like to work on a -1000 to +1000 scale, it means we can have an integer box size of say 250 instead of a fraction such as 0.25, I just think it&#8217;s easier to read.<br />
So you&#8217;ll now have a stored proc similar to this.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocDimensionsForMeasureGroup]
    (@Catalog       VARCHAR(255)
    ,@Cube          VARCHAR(255)
    ,@MeasureGroup  VARCHAR(255)
    )
AS

 DECLARE @BoxSize INT
 DECLARE @Stretch FLOAT
 SET @BoxSize = 250
 SET @Stretch = 1.4

;WITH BaseData AS
(
    SELECT
          mgd.*
        , d.[DESCRIPTION]
        , REPLACE(REPLACE(CAST(mgd.[DIMENSION_UNIQUE_NAME]
              AS VARCHAR(255))
              ,'[',''),']','') AS DimensionCaption
        , REPLACE(REPLACE(CAST(mgd.[MEASUREGROUP_NAME]
              AS VARCHAR(255))
              ,'[',''),']','') AS MeasureGroupCaption
    FROM OPENQUERY(CubeLinkedServer, 'SELECT
                [CATALOG_NAME]
                   +[CUBE_NAME]
                   +[MEASUREGROUP_NAME]
                   +[DIMENSION_UNIQUE_NAME] AS Seq
                , [CATALOG_NAME]
                , [CUBE_NAME]
                , [MEASUREGROUP_NAME]
                , [MEASUREGROUP_CARDINALITY]
                , [DIMENSION_UNIQUE_NAME]
                , [DIMENSION_CARDINALITY]
                , [DIMENSION_IS_VISIBLE]
                , [DIMENSION_IS_FACT_DIMENSION]
                , [DIMENSION_GRANULARITY]
            FROM $SYSTEM.MDSCHEMA_MEASUREGROUP_DIMENSIONS
            WHERE [DIMENSION_IS_VISIBLE]') mgd
        INNER JOIN OPENQUERY(CubeLinkedServer, 'SELECT
                [CATALOG_NAME]
                ,[CUBE_NAME]
                ,[DIMENSION_UNIQUE_NAME]
                ,[DESCRIPTION]
            FROM $SYSTEM.MDSCHEMA_DIMENSIONS
            WHERE [DIMENSION_IS_VISIBLE]') d
                ON  CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))
                  = CAST(d.[CATALOG_NAME] AS VARCHAR(255))
                AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))
                  = CAST(d.[CUBE_NAME] AS VARCHAR(255))
                AND CAST(mgd.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
                  = CAST(d.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
     WHERE  CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))      = @Catalog
        AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))         = @Cube
        AND CAST(mgd.[MEASUREGROUP_NAME] AS VARCHAR(255)) = @MeasureGroup
)
,TotCount AS
(
    SELECT COUNT(*) AS RecCount FROM BaseData
)
, RecCount AS
(
    SELECT RANK() OVER (ORDER BY CAST(Seq AS VARCHAR(255))) AS RecID
        , RecCount
        , BaseData.*
    FROM
        BaseData CROSS JOIN TotCount
)
, Angles AS
(
    SELECT
        *
        , SIN(RADIANS((CAST(RecID    AS FLOAT)
             /CAST(RecCount AS FLOAT))
             * 360)) * 1000 AS x
        , COS(RADIANS((CAST(RecID AS FLOAT)
             /CAST(RecCount AS FLOAT))
             * 360)) * 1000 AS y
    FROM RecCount
)
,Results AS
(
    SELECT
        *
        , geometry::STGeomFromText('POINT('
            + CAST(y AS VARCHAR(20))
            + ' '
            + CAST(x AS VARCHAR(20))
            + ')',4326) AS Posn
        , geometry::STPolyFromText('POLYGON ((' +
            CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)-@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)-@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + '
            ))',0) AS Box
    FROM Angles
)
SELECT * FROM Results
GO
</pre>
<p>If you then execute this in Management Studio, you&#8217;ll notice an extra tab in the result window called Spatial Results.</p>
<pre>
EXEC [dbo].[upCubeDocDimensionsForMeasureGroup]
    @Catalog = 'Adventure Works DW 2008R2',
    @Cube = 'Adventure Works',
    @MeasureGroup = 'Financial Reporting'
</pre>
<p>Click on the Spatial Results tab, then select Box as the spatial column, and you&#8217;ll see the boxes that we&#8217;ve created in a preview window.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_13.png" alt="" /></p>
<p>This is now getting somewhere close. But as well as the dimensions, we also want to show the measure group in the middle, as well as lines linking them together to actuallly create our star. We can do this by adding a couple more geometry functions to our query. We end up with the end of our proc looking like this.</p>
<pre>
,Results AS
(
    SELECT
        *
        , geometry::STGeomFromText('POINT('
            + CAST(y AS VARCHAR(20))
            + ' '
            + CAST(x AS VARCHAR(20))
            + ')',4326) AS Posn
        , geometry::STPolyFromText('POLYGON ((' +
            CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)-@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)-@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST((y*@Stretch)+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(x+(@BoxSize/2) AS VARCHAR(20)) + '
            ))',0) AS Box
         , geometry::STLineFromText('LINESTRING (0 0, '
              + CAST((y*@Stretch) AS VARCHAR(20))
              + ' ' + CAST(x AS VARCHAR(20))
              + ')', 0) AS Line
         , geometry::STPolyFromText('POLYGON ((' +
            CAST(0+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(0+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST(0-@BoxSize AS VARCHAR(20)) + ' '
              + CAST(0+(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST(0-@BoxSize AS VARCHAR(20)) + ' '
              + CAST(0-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST(0+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(0-(@BoxSize/2) AS VARCHAR(20)) + ', ' +
            CAST(0+@BoxSize AS VARCHAR(20)) + ' '
              + CAST(0+(@BoxSize/2) AS VARCHAR(20)) + '
            ))',0) AS CenterBox

    FROM Angles
)
SELECT * FROM Results
GO
</pre>
<p>So, we&#8217;ve now got the polygons and lines being generated by the proc, it&#8217;s now time to add them into the report and display them to our users.</p>
<p>Firstly open up the CubeDoc_MeasureGroup.rdl report, and go to the properties of the dsDimensions dataset. Click Refresh Fields and the new x, y, Posn, Box, Line and CenterBox fields should now be available.<br />
Then drag a Map from the toolbox onto the report. This will start the map wizard.<br />
Select SQL Server Spatial Query as the source for the data and click Next.<br />
Choose dsDimensions as the dataset to use for the data and click Next.<br />
Choose Box as the spatial field, and Polygon as the layer type. It may well give you an error, just ignore it.<br />
Don&#8217;t select the embed map or Bing maps layer.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_14.png" alt="" /></p>
<p>Click Next, then select a basic map, select the default options for the remaining wizard stages and you&#8217;ll end up with a map in your report.</p>
<p>If you preview the report at this stage you won&#8217;t see the polygons. This is because the map still thinks it&#8217;s a geographical map, and it is trying to draw our boxes as latitude and longitudes. We don&#8217;t want this, but want it to show them on our own scale. To fix this, just change the CoordinateSystem property of the map from Geographic to Planar.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_15.png" alt="" /></p>
<p>You can then preview the report, which should show you something like this</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_16.png" alt="" /></p>
<p>It still doesn&#8217;t look like a star but we&#8217;ve still got a few more changes to make. We need to add a couple more layers to the map, the center box for the measure group and then the lines to link them all together.<br />
Add a new polygon layer to the map and then set the layer data to use the CenterBox field from the dsDimensions dataset.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_17.png" alt="" /></p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_18.png" alt="" /></p>
<p>Repeat the above, but with a new line layer instead of a polygon layer. Set the layer data to use the Line field of dsDimensions.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_19.png" alt="" /></p>
<p>To move the lines behind the boxes, just rearrange the order of the layers by using the blue arrows in the map layers window. We want the Lines layer to be at the bottom.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_20.png" alt="" /></p>
<p>Set the PolygonTemplate.BackgroundColour property of the CenterBox layer to Khaki, and the set the same property of the dimension box layer to LightBlue.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_21.png" alt="" /></p>
<p>Then set the PolygonTemplate.Label property of the CenterBox layer to the MeasureGroupCaption field, and set the ShowLabel property to True. If you don&#8217;t then SSRS will decide whether or not to show the label, we want it to always show.<br />
Set the PolygonTemplate.Label property of the Dimension layer to the DimensionCaption field, and set the ShowLabel property to True.<br />
You can then play around with font sizes, zoom, background colours, line widths etc. to get the effects that you want, but you&#8217;ll end up with a star schema visualisation similar to this.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_22.png" alt="" /></p>
<p>You can also configure actions for each layer. Using this you can hyperlink each dimension box to show the CubeDoc_Dimension report for the selected dimension etc, making the star schema interactive.</p>
<p>This has been quite a fun blog post to investigate, I hope you can take something useful from it and have as much fun with it as I&#8217;ve had with it. Every demo that I&#8217;ve seen using spatial data has been using maps, hopefully this shows an alternative use beyond geographical mapping.</p>
<ul>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/">Part 1 &#8211; Creating the DMV stored procs</a></li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/">Part 2 &#8211; Create the SSRS reports</a></li>
<li>Part 3 &#8211; Use spatial data and maps to create a star schema view</li>
<li><a onclick="pageTracker._trackPageview('/downloads/CubeDoc.zip'); " href="/download/blog/CubeDoc.zip" target="_self">Download Source Code</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>OLAP Cube Documentation in SSRS part 2</title>
		<link>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/</link>
		<comments>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/#comments</comments>
		<pubDate>Mon, 27 Sep 2010 13:27:16 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Business Intelligence]]></category>
		<category><![CDATA[Reporting Services]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[BUS Matrix]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[Documentation]]></category>
		<category><![CDATA[Kimball]]></category>
		<category><![CDATA[Map]]></category>
		<category><![CDATA[OLAP]]></category>
		<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SSAS]]></category>
		<category><![CDATA[SSRS]]></category>
		<category><![CDATA[Star Schema]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=202</guid>
		<description><![CDATA[In my previous post I described how to create a number of stored procedures that use Dynamic Management Views (DMVs) to return the metadata structure of an SSAS 2008 OLAP cube, including dimensions, attributes, measure groups, BUS matrix etc. In this post I&#8217;m going to use those procs to create a set of SSRS 2008 [...]]]></description>
			<content:encoded><![CDATA[<p>In my <a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/">previous post</a> I described how to create a number of stored procedures that use Dynamic Management Views (DMVs) to return the metadata structure of an SSAS 2008 OLAP cube, including dimensions, attributes, measure groups, BUS matrix etc.</p>
<p>In this post I&#8217;m going to use those procs to create a set of SSRS 2008 reports that will serve as the automated documentation of your cube. I&#8217;m going to make the following assumptions:</p>
<ul>
<li>You&#8217;ve read the part 1 post, and already have the stored procs in a database.</li>
<li>You know the basics of SSRS 2008</li>
</ul>
<p>If you haven&#8217;t read part 1, you can jump to it here.</p>
<ul>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/">Part 1 &#8211; Creating the DMV stored procs</a></li>
<li>Part 2 &#8211; Create the SSRS reports</li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/">Part 3 &#8211; Use spatial data and maps to create a star schema view</a></li>
<li><a onclick="pageTracker._trackPageview('/downloads/CubeDoc.zip'); " href="/download/blog/CubeDoc.zip" target="_self">Download Source Code</a></li>
</ul>
<p>﻿<strong>UPDATE</strong>: I presented a 1 hour session at SQLBits 8 covering this work, <a href="http://sqlbits.com/Sessions/Event8/Automating_SSAS_cube_documentation_using_SSRS_DMV_and_Spatial_Data" target="_blank">you can watch the video here</a>.</p>
<p>Firstly I create a basic template report which has an appropriate header, with date/time stamp, server name and logos etc. This means that all of the reports will have a common look and feel. I could of course make use of the new report parts in SSRS 2008 R2 for this, but to maintain compatibility with pre R2 I&#8217;ll keep it simple.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_1.png" alt="" /></p>
<p>The expression in the box on the top right is just to display today&#8217;s date in UK format dd/mm/yyyy.</p>
<pre>  =FORMAT(Today(),"dd/MM/yyyy")</pre>
<p>The reports that we&#8217;ll build will include the following:</p>
<ul>
<li>CubeDoc_Cubes.rdl &#8211; Entry page, list of all cubes in the database</li>
<li>CubeDoc_Cube.rdl &#8211; showing all measure groups and dimensions within the selected cube</li>
<li>CubeDoc_Dimension.rdl &#8211; showing all hierarchies, attributes and related measure groups</li>
<li>CubeDoc_MeasureGroup.rdl &#8211; showing all measures and related dimensions</li>
<li>CubeDoc_Search.rdl &#8211; search names and descriptions of cubes, dimensions, measure groups, etc</li>
</ul>
<p>Create the first report (CubeDoc_Cubes.rdl) which will act as the entry screen and menu of cubes.<br />
Add a dataset dsCubes, and point it at the stored proc dbo.upCubeDocCubes</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_dsCubes.png" alt="" /></p>
<p>The proc has a @Catalog parameter, which filters the result set to a specific SSAS database (catalog). We want to return all cubes from all catalogs, so set the parameter value to =Nothing</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_dsCubes2.png" alt="" /></p>
<p>All we have to do now is add a table and pull in the dataset fields that we want to see.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_2.png" alt="" /></p>
<p>We can then preview the report to test that it returns the right list of cubes. You should see something like this.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_3.png" alt="" /></p>
<p>Note that the AdventureWorks database doesn&#8217;t contain any descriptions, so you won&#8217;t see any in the report but they will be there when you add descriptions to your own cubes.</p>
<p>The next report we&#8217;re going to write is the CubeDoc_Cube report, which will list the measure groups, dimensions and BUS matrix of a single cube. We&#8217;ll link the two reports together later on.</p>
<p>Create a new report, using the template report you created earlier (select the template report in the solution explorer window, then CTRL+C then CTRL+V) and rename the new file as CubeDoc_Cube.rdl.</p>
<p>Add a report parameter called @Catalog which should be Text. I&#8217;ve set mine to default to &#8220;Adventure Works DW 2008R2&#8243; to make testing easier.</p>
<p>Add a dataset called dsCubes, and point it at the dbo.upCubeDocCubes proc, and link the @Catalog dataset parameter to the @Catalog report parameter.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_4.png" alt="" /></p>
<p>This dataset will query the available cubes for the selected catalog, and populate a new parameter which we&#8217;ll now create, called @Cube. This should also be a text parameter, but this time we&#8217;ll set the available values to those returned by the dsCubes dataset.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_5.png" alt="" /></p>
<p>If you want you can also set the default value of the parameter to the CUBE_NAME field of dsCubes. This parameter is not a multi value parameter, so by defaulting it to the dataset it will just default to the first record.</p>
<p>We can now use @Catalog and @Cube parameters to query the available measure groups and dimensions.<br />
So, create a three new datasets:</p>
<ul>
<li>dsDimensions &#8211; pointing to dbo.upCubeDocDimensionsInCube</li>
<li>dsMeasureGroups &#8211; pointing to dbo.upCubeDocMeasureGroupsInCube</li>
<li>dsBusMatrix &#8211; pointing to dbo.upCubeDocBUSMatrix</li>
</ul>
<p>Set each of their @Catalog parameters to the report&#8217;s @Catalog parameter, and their @Cube parameters to the report&#8217;s @Cube parameter.</p>
<p>Create two tables in the report, one for measure groups and one for dimensions. Drag in the fields that you want to see, and preview the report. You should see something like this.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_6.png" alt="" /></p>
<p>I&#8217;ve added a couple of textbox titles for good measure.</p>
<p>The third dataset, dsBUSMatrix requires something a little more interesting. For those that aren&#8217;t familiar with Kimball&#8217;s BUS Matrix concept, it&#8217;s a grid that shows the relationship and connectivity between facts (measure groups) and their dimensions. As the name suggests, we&#8217;ll use SSRS&#8217;s Matrix control for this. Once you&#8217;ve added a matrix control onto the report, follow these steps (using the dsBUSMatrix dataset):</p>
<ul>
<li>Drag DIMENSION_UNIQUE_NAME onto the Rows of the matrix</li>
<li>Drag MEASUREGROUP_NAME onto the columns of the matrix</li>
<li>Right click on the Data box of the matrix, and type in the expression below. This checks the cardinality of the dimensions/measures to determine whether it is a regular relationship, a many to many or a degenerate fact</li>
</ul>
<pre>=SWITCH(SUM(Fields!Relationship.Value)=0,"",
   Fields!DIMENSION_IS_FACT_DIMENSION.Value=1,"F",
   Fields!MEASUREGROUP_CARDINALITY.Value="MANY","M",
   True,"X")</pre>
<p>This will either show an X if there is a regular relationship, or show an M or F if there&#8217;s a many to many or degenerate relationship respectively.<br />
To make it easier to read, I also like to set the background colour of the Data textbox to highlight the type of relationship further.</p>
<pre>=SWITCH(SUM(Fields!Relationship.Value)=0,"Transparent",
   Fields!DIMENSION_IS_FACT_DIMENSION.Value=1,"Yellow",
   Fields!MEASUREGROUP_CARDINALITY.Value="MANY","Red",
   True,"CornflowerBlue")</pre>
<p>If you preview the report you should see the following</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_7.png" alt="" /></p>
<p>It shows the concept, but it needs a little tidying up. Centering the text in the data textbox helps, but we can also use a fantastic new feature in SSRS 2008 R2 to rotate the column titles. Simply set the WritingMode property in the Localization group to Rotate270, and then shrink the width of the column.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_8.png" alt="" /></p>
<p>I&#8217;ve also added a title, with a key, and level of row grouping using the DIMENSION_MASTER_NAME field, which groups role playing dimensions by their master dimension. It should now look something like this.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_9.png" alt="" /></p>
<p>That&#8217;s it for this report, so save it, then go back to the first report (CubeDoc_Cubes.rdl) and right click, properties on the [CUBE_NAME] textbox. Go to the action tab, and set the action to navigate to the CubeDoc_Cube report, passing through the CATALOG_NAME and CUBE_NAME fields from the dataset as the parameter values. This sets up a hyperlink from one report to the other, allowing users to navigate around the cube doc reports by clicking on what they want to know about.</p>
<p><img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_10.png" alt="" /></p>
<p>We then need to do the same for 3 other reports:<br />
CubeDoc_Dimensions</p>
<ul>
<li>dbo.upCubeDocAttributesInDimension results in a table</li>
<li>dbo.upCubeDocMeasureGroupsForDimension results in a table</li>
<li>Add an extra @Dimension parameter, populated from dbo.upCubeDocDimensionsInCube</li>
</ul>
<p>CubeDoc_MeasureGroup</p>
<ul>
<li>dbo.upCubeDocMeasuresInMeasureGroup results in a table</li>
<li>dbo.upCubeDocDimensionsForMeasureGroup results in a table</li>
<li>Add an extra @MeasureGroup parameter, populated from dbo.upCubeDocMeasureGroupsInCube</li>
</ul>
<p>CubeDoc_Search</p>
<ul>
<li>Add an extra @Search parameter, text, with no default</li>
<li>Table containing results from dbo.upCubeDocSearch, using the @Search parameter</li>
</ul>
<p>Link all appropriate textboxes (measure groups, dimensions, search etc.) to their relevant report using the report action, and hey presto &#8211; a fully automated, real time, self-documenting cube report.</p>
<p>In the next and final installment of this series of blog posts, we&#8217;ll explore SQL 2008&#8242;s spatial data to generate an automated star schema visualisation to add that little something extra to the reports.</p>
<ul>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/">Part 1 &#8211; Creating the DMV stored procs</a></li>
<li>Part 2 &#8211; Create the SSRS reports</li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/">Part 3 &#8211; Use spatial data and maps to create a star schema view</a></li>
<li><a onclick="pageTracker._trackPageview('/downloads/CubeDoc.zip'); " href="/download/blog/CubeDoc.zip" target="_self">Download Source Code</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>OLAP Cube Documentation in SSRS part 1</title>
		<link>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/</link>
		<comments>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/#comments</comments>
		<pubDate>Sat, 25 Sep 2010 22:25:47 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Business Intelligence]]></category>
		<category><![CDATA[Reporting Services]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[BUS Matrix]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[Documentation]]></category>
		<category><![CDATA[Kimball]]></category>
		<category><![CDATA[Map]]></category>
		<category><![CDATA[OLAP]]></category>
		<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SSAS]]></category>
		<category><![CDATA[SSRS]]></category>
		<category><![CDATA[Star Schema]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=190</guid>
		<description><![CDATA[Being a business intelligence consultant, I like to spend my time designing data warehouses, ETL scripts and OLAP cubes. An unfortunate consequence of this is having to write the documentation that goes with the fun techy work. So it got me thnking, is there a slightly more fun techy way of automating the documentation of [...]]]></description>
			<content:encoded><![CDATA[<p>Being a business intelligence consultant, I like to spend my time designing data warehouses, ETL scripts and OLAP cubes. An unfortunate consequence of this is having to write the documentation that goes with the fun techy work. So it got me thnking, is there a slightly more fun techy way of automating the documentation of OLAP cubes&#8230;</p>
<p>There are some good tools out there such as <a href="http://bidocumenter.com" target="_blank">BI Documenter</a>, but I wanted a way of having more control over the output, and also automating it further so that you don&#8217;t have to run an overnight build of the documentation.</p>
<p>I found a great article by <a href="http://dwbi1.wordpress.com/2010/01/01/ssas-dmv-dynamic-management-view/" target="_blank">Vincent Rainardi</a> describing some DMVs (Dynamic Management Views) available in SQL 2008 which got me thinking, why not just build a number of SSRS reports calling these DMVs, which would then dynamically create the cube structure documentation in real time whenever the report rendered..</p>
<p>This post is the first in a 3 part set which will demonstrate how you can use these DMVs to automate the SSAS cube documentation and user guide.</p>
<ul>
<li>Part 1 &#8211; Creating the DMV stored procs</li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/">Part 2 &#8211; Create the SSRS reports</a></li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/">Part 3 &#8211; Use spatial data and maps to create a star schema view</a></li>
<li><a onclick="pageTracker._trackPageview('/downloads/CubeDoc.zip'); " href="/download/blog/CubeDoc.zip" target="_self">Download Source Code</a></li>
</ul>
<p><strong>UPDATE</strong>: I presented a 1 hour session at SQLBits 8 covering all of this work, <a href="http://sqlbits.com/Sessions/Event8/Automating_SSAS_cube_documentation_using_SSRS_DMV_and_Spatial_Data" target="_blank">you can watch the video here</a>.</p>
<p>There&#8217;s a full list of <a href="http://msdn.microsoft.com/en-us/library/ms126079.aspx" target="_blank">DMVs available in SQL 2008 R2</a> on the msdn site.</p>
<p>The primary DMVs that are of interest are:</p>
<table style="font-size: 0.8em;">
<tbody>
<tr>
<td><strong>DMV</strong></td>
<td><strong>Description</strong></td>
</tr>
<tr>
<td>MDSCHEMA_CUBES</td>
<td>Lists the cubes in an SSAS database</td>
</tr>
<tr>
<td>MDSCHEMA_MEASUREGROUPS</td>
<td>Lists measure groups</td>
</tr>
<tr>
<td>MDSCHEMA_DIMENSIONS</td>
<td>Lists dimensions</td>
</tr>
<tr>
<td>MDSCHEMA_LEVELS</td>
<td>Dimension attributes</td>
</tr>
<tr>
<td>MDSCHEMA_MEASUREGROUP_DIMENSIONS</td>
<td>Enumerates dimensions of measure groups</td>
</tr>
<tr>
<td>MDSCHEMA_MEASURES</td>
<td>Lists measures</td>
</tr>
</tbody>
</table>
<p>When querying DMVs we can use SQL style SELECT statements, but executed against the cube in a DMX window.</p>
<pre>SELECT *
FROM $SYSTEM.MDSCHEMA_CUBES</pre>
<p>This returns a dataset like any other SQL query.<br />
<img style="text-align: center;" src="\images\blog\SSASCubeDocSSRS_23.png" alt="" /></p>
<p>We can even enhance it with DISTINCT and WHERE clauses, although they are more restricted than basic SQL. One of the main limitations is the lack of a JOIN operator. A number of the queries that I&#8217;ll perform below need to use JOIN, so to get around this I wrap up each query in an SQL OPENROWSET command, executed against a SQL database with a linked server to the cube. This enables me to perform JOINs using queries such as</p>
<pre>
SELECT *
FROM OPENQUERY(CubeLinkedServer,
   'SELECT *
    FROM $SYSTEM.MDSCHEMA_MEASUREGROUP_DIMENSIONS') mgd
INNER JOIN OPENQUERY(CubeLinkedServer,
   'SELECT *
    FROM $SYSTEM.MDSCHEMA_MEASUREGROUPS') mg
ON mgd.XXX = mg.XXX
</pre>
<p>etc.</p>
<p>I&#8217;m therefore going to create a number of stored procs to wrap up this functionality, the SSRS reports can then just call the procs.</p>
<p>Within BIDS, every item (cube, measure group, measure, dimension, attribute, hierarchy, KPI, etc.) has a description in the properties pane which is a multi-line free text property. These are exposed by the DMVs, so I&#8217;m going to make use of them and bring them out in the reports. This allows you to create the descriptions within BIDS as you&#8217;re developing the cube, meaning they&#8217;re version controlled and always in sync with the code.</p>
<p>I should also point out that I&#8217;m using SQL Server 2008 R2. All of the queries below will work with SQL 2008, but I want to use the spatial report functionality of SSRS 2008 R2 to generate dynamic star schema visualisations, which is only supported in R2.</p>
<p>In this post I&#8217;ll script out the stored procedures used as the basis of the documentation. In my next post I&#8217;ll put these into SSRS reports.</p>
<p>Lets get started.</p>
<p>Firstly we need to create our linked server. This script will create a linked server called CubeLinkedServer pointing to the Adventure Works DW 2008R2 OLAP database on the local server.</p>
<pre>
EXEC master.dbo.sp_addlinkedserver
   @server = N'CubeLinkedServer',
   @srvproduct=N'MSOLAP',
   @provider=N'MSOLAP',
   @datasrc=N'(local)',
   @catalog=N'Adventure Works DW 2008R2'
</pre>
<p>You&#8217;ll have to set up the security according to your requirements. So now lets start creating the source procs.</p>
<p>The first proc lists all of the cubes. The MDSCHEMA_CUBES DMV returns not only cubes, but also dimensions, I&#8217;m filtering it to only return cubes by specifying CUBE_SOURCE=1.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocCubes]
  (@Catalog       VARCHAR(255) = NULL
  )
AS
  SELECT *
  FROM OPENQUERY(CubeLinkedServer,
    'SELECT *
     FROM $SYSTEM.MDSCHEMA_CUBES
     WHERE CUBE_SOURCE = 1')
  WHERE CAST([CATALOG_NAME] AS VARCHAR(255)) = @Catalog
    OR @Catalog IS NULL
GO
</pre>
<p>The next proc returns all measure groups found within a specified cube.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocMeasureGroupsInCube]
  (@Catalog       VARCHAR(255)
  ,@Cube          VARCHAR(255)
  )
AS
  SELECT *
  FROM OPENQUERY(CubeLinkedServer,
    'SELECT *
     FROM $SYSTEM.MDSCHEMA_MEASUREGROUPS ')
  WHERE CAST([CATALOG_NAME] AS VARCHAR(255)) = @Catalog
    AND CAST([CUBE_NAME] AS VARCHAR(255))    = @Cube
GO
</pre>
<p>This next proc returns a list of measures within a specified measure group.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocMeasuresInMeasureGroup]
  (@Catalog       VARCHAR(255)
  ,@Cube          VARCHAR(255)
  ,@MeasureGroup  VARCHAR(255)
  )
AS
SELECT * FROM OPENQUERY(CubeLinkedServer,
  'SELECT *
   FROM $SYSTEM.MDSCHEMA_MEASURES
     WHERE [MEASURE_IS_VISIBLE]')
   WHERE CAST([CATALOG_NAME] AS VARCHAR(255))      = @Catalog
     AND CAST([CUBE_NAME] AS VARCHAR(255))         = @Cube
     AND CAST([MEASUREGROUP_NAME] AS VARCHAR(255)) = @MeasureGroup
GO
</pre>
<p>The following proc queries all dimensions available within a specified cube. I&#8217;m filtering using the DIMENSION_IS_VISIBLE column to only show visible dimensions.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocDimensionsInCube]
  (@Catalog       VARCHAR(255)
  ,@Cube          VARCHAR(255)
  )
AS
SELECT * FROM OPENQUERY(CubeLinkedServer,
  'SELECT *
   FROM $SYSTEM.MDSCHEMA_DIMENSIONS
     WHERE [DIMENSION_IS_VISIBLE]')
   WHERE CAST([CATALOG_NAME] AS VARCHAR(255)) = @Catalog
     AND CAST([CUBE_NAME] AS VARCHAR(255))    = @Cube
GO
</pre>
<p>Then we can query all available attributes within a dimension. This DMV returns a bitmask field (LEVEL_ORIGIN) which defines whether the attribute is a key, attribute or hierarchy. I&#8217;m using bitwise AND (&amp;) to split this into three seperate fields for ease of use. I&#8217;m also filtering out invisible attributes, as well as those with a level of 0. Level 0 is the [All] member of any attribute, which we can ignore for this purpose.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocAttributesInDimension]
  (@Catalog       VARCHAR(255)
  ,@Cube          VARCHAR(255)
  ,@Dimension  VARCHAR(255)
  )
AS
  SELECT *
    , CASE WHEN CAST([LEVEL_ORIGIN] AS INT) &amp; 1 = 1
        THEN 1 ELSE 0 END AS IsHierarchy
    , CASE WHEN CAST([LEVEL_ORIGIN] AS INT) &amp; 2 = 2
        THEN 1 ELSE 0 END AS IsAttribute
    , CASE WHEN CAST([LEVEL_ORIGIN] AS INT) &amp; 4 = 4
        THEN 1 ELSE 0 END AS IsKey
  FROM OPENQUERY(CubeLinkedServer,
    'SELECT *
     FROM $SYSTEM.MDSCHEMA_LEVELS
     WHERE [LEVEL_NUMBER]&gt;0
       AND [LEVEL_IS_VISIBLE]')
  WHERE CAST([CATALOG_NAME] AS VARCHAR(255))          = @Catalog
    AND CAST([CUBE_NAME] AS VARCHAR(255))             = @Cube
    AND CAST([DIMENSION_UNIQUE_NAME] AS VARCHAR(255)) = @Dimension
GO
</pre>
<p>The next proc returns measure groups with their associated dimensions. We have to join two DMVs together in order to get the description columns of both the dimension and measure group.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocMeasureGroupsForDimension]
    (@Catalog       VARCHAR(255)
    ,@Cube          VARCHAR(255)
    ,@Dimension     VARCHAR(255)
    )
AS
  SELECT
    mgd.*
    , m.[DESCRIPTION]
  FROM OPENQUERY(CubeLinkedServer,
    'SELECT
       [CATALOG_NAME]
       , [CUBE_NAME]
       , [MEASUREGROUP_NAME]
       , [MEASUREGROUP_CARDINALITY]
       , [DIMENSION_UNIQUE_NAME]
     FROM $SYSTEM.MDSCHEMA_MEASUREGROUP_DIMENSIONS
       WHERE [DIMENSION_IS_VISIBLE]') mgd
   INNER JOIN OPENQUERY(CubeLinkedServer,
     'SELECT
       [CATALOG_NAME]
       ,[CUBE_NAME]
       ,[MEASUREGROUP_NAME]
       ,[DESCRIPTION]
     FROM $SYSTEM.MDSCHEMA_MEASUREGROUPS') mg
        ON  CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))
           = CAST(mg.[CATALOG_NAME] AS VARCHAR(255))
        AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))
           = CAST(mg.[CUBE_NAME] AS VARCHAR(255))
        AND CAST(mgd.[MEASUREGROUP_NAME] AS VARCHAR(255))
           = CAST(mg.[MEASUREGROUP_NAME] AS VARCHAR(255))
  WHERE CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))            = @Catalog
    AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))               = @Cube
    AND CAST(mgd.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))   = @Dimension
GO
</pre>
<p>The next proc is similar to the above, but the opposite way around. It returns all dimensions that are related to a measure group.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocDimensionsForMeasureGroup]
  (@Catalog       VARCHAR(255)
  ,@Cube          VARCHAR(255)
  ,@MeasureGroup  VARCHAR(255)
  )
AS
  SELECT
    mgd.*
    , d.[DESCRIPTION]
  FROM OPENQUERY(CubeLinkedServer,
    'SELECT
        [CATALOG_NAME]
       ,[CUBE_NAME]
       ,[MEASUREGROUP_NAME]
       ,[MEASUREGROUP_CARDINALITY]
       ,[DIMENSION_UNIQUE_NAME]
       ,[DIMENSION_CARDINALITY]
       ,[DIMENSION_IS_VISIBLE]
       ,[DIMENSION_IS_FACT_DIMENSION]
       ,[DIMENSION_GRANULARITY]
     FROM $SYSTEM.MDSCHEMA_MEASUREGROUP_DIMENSIONS
       WHERE [DIMENSION_IS_VISIBLE]') mgd
  INNER JOIN OPENQUERY(CubeLinkedServer,
    'SELECT
       [CATALOG_NAME]
       ,[CUBE_NAME]
       ,[DIMENSION_UNIQUE_NAME]
       ,[DESCRIPTION]
     FROM $SYSTEM.MDSCHEMA_DIMENSIONS
       WHERE [DIMENSION_IS_VISIBLE]') d
   ON  CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))
       = CAST(d.[CATALOG_NAME] AS VARCHAR(255))
   AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))
       = CAST(d.[CUBE_NAME] AS VARCHAR(255))
   AND CAST(mgd.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
       = CAST(d.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
  WHERE  CAST(mgd.[CATALOG_NAME] AS VARCHAR(255))        = @Catalog
     AND CAST(mgd.[CUBE_NAME] AS VARCHAR(255))           = @Cube
     AND CAST(mgd.[MEASUREGROUP_NAME] AS VARCHAR(255))   = @MeasureGroup
GO
</pre>
<p>The next proc builds a BUS matrix, joining every dimension to its related measure groups. Later we&#8217;ll use the SSRS tablix control to pivot this into matrix form.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocBUSMatrix]
    (@Catalog       VARCHAR(255),
     @Cube          VARCHAR(255)
    )
AS
  SELECT
     bus.[CATALOG_NAME]
    ,bus.[CUBE_NAME]
    ,bus.[MEASUREGROUP_NAME]
    ,bus.[MEASUREGROUP_CARDINALITY]
    ,bus.[DIMENSION_UNIQUE_NAME]
    ,bus.[DIMENSION_CARDINALITY]
    ,bus.[DIMENSION_IS_FACT_DIMENSION]
    ,bus.[DIMENSION_GRANULARITY]
    ,dim.[DIMENSION_MASTER_NAME]
    ,1 AS Relationship
  FROM
    OPENQUERY(CubeLinkedServer,
      'SELECT
        [CATALOG_NAME]
        ,[CUBE_NAME]
        ,[MEASUREGROUP_NAME]
        ,[MEASUREGROUP_CARDINALITY]
        ,[DIMENSION_UNIQUE_NAME]
        ,[DIMENSION_CARDINALITY]
        ,[DIMENSION_IS_FACT_DIMENSION]
        ,[DIMENSION_GRANULARITY]
       FROM $SYSTEM.MDSCHEMA_MEASUREGROUP_DIMENSIONS
        WHERE [DIMENSION_IS_VISIBLE]') bus
    INNER JOIN OPENQUERY(CubeLinkedServer,
      'SELECT
        [CATALOG_NAME]
        ,[CUBE_NAME]
        ,[DIMENSION_UNIQUE_NAME]
        ,[DIMENSION_MASTER_NAME]
       FROM $SYSTEM.MDSCHEMA_DIMENSIONS') dim
    ON CAST(bus.[CATALOG_NAME] AS VARCHAR(255))
     = CAST(dim.[CATALOG_NAME] AS VARCHAR(255))
    AND CAST(bus.[CUBE_NAME] AS VARCHAR(255))
     = CAST(dim.[CUBE_NAME] AS VARCHAR(255))
    AND CAST(bus.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
     = CAST(dim.[DIMENSION_UNIQUE_NAME] AS VARCHAR(255))
  WHERE  CAST(bus.[CATALOG_NAME] AS VARCHAR(255)) = @Catalog
     AND CAST(bus.[CUBE_NAME] AS VARCHAR(255)) = @Cube
GO
</pre>
<p>Next, in order to make it easier for users to find items within the cube, I&#8217;ve created a searching proc which will scour a number of the DMVs for anything containing the search term.</p>
<pre>
CREATE PROCEDURE [dbo].[upCubeDocSearch]
    (@Search        VARCHAR(255)
    ,@Catalog       VARCHAR(255)=NULL
    ,@Cube          VARCHAR(255)=NULL
    )
AS
  WITH MetaData AS
  (
   --Cubes
    SELECT CAST('Cube' AS VARCHAR(20))            AS [Type]
      , CAST(CATALOG_NAME AS VARCHAR(255))     AS [Catalog]
      , CAST(CUBE_NAME AS VARCHAR(255))           AS [Cube]
      , CAST(CUBE_NAME AS VARCHAR(255))           AS [Name]
      , CAST(DESCRIPTION AS VARCHAR(4000)) AS [Description]
      , CAST(CUBE_NAME AS VARCHAR(255))           AS [Link]
    FROM OPENQUERY(CubeLinkedServer,
      'SELECT [CATALOG_NAME], [CUBE_NAME], [DESCRIPTION]
       FROM $SYSTEM.MDSCHEMA_CUBES
       WHERE CUBE_SOURCE = 1')
    WHERE  (CAST([CATALOG_NAME] AS VARCHAR(255))
       = @Catalog OR @Catalog IS NULL)

    UNION ALL

   --Dimensions
    SELECT CAST('Dimension' AS VARCHAR(20))         AS [Type]
      , CAST(CATALOG_NAME AS VARCHAR(255))       AS [Catalog]
      , CAST(CUBE_NAME AS VARCHAR(255))             AS [Cube]
      , CAST(DIMENSION_NAME AS VARCHAR(255))        AS [Name]
      , CAST(DESCRIPTION AS VARCHAR(4000))   AS [Description]
      , CAST(DIMENSION_UNIQUE_NAME AS VARCHAR(255)) AS [Link]
    FROM OPENQUERY(CubeLinkedServer,
      'SELECT [CATALOG_NAME], [CUBE_NAME]
          , [DIMENSION_NAME], [DESCRIPTION]
          , [DIMENSION_UNIQUE_NAME]
       FROM $SYSTEM.MDSCHEMA_DIMENSIONS
         WHERE [DIMENSION_IS_VISIBLE]')
    WHERE  (CAST([CATALOG_NAME] AS VARCHAR(255))
        = @Catalog OR @Catalog IS NULL)
      AND (CAST([CUBE_NAME] AS VARCHAR(255))
        = @Cube OR @Cube IS NULL)
      AND LEFT(CAST(CUBE_NAME AS VARCHAR(255)),1)
        &lt;&gt;'$' --Filter out dimensions not in a cube

    UNION ALL

   --Attributes
    SELECT CAST('Attribute' AS VARCHAR(20))         AS [Type]
      , CAST(CATALOG_NAME AS VARCHAR(255))       AS [Catalog]
      , CAST(CUBE_NAME AS VARCHAR(255))             AS [Cube]
      , CAST(LEVEL_CAPTION AS VARCHAR(255))         AS [Name]
      , CAST(DESCRIPTION AS VARCHAR(4000))   AS [Description]
      , CAST(DIMENSION_UNIQUE_NAME AS VARCHAR(255)) AS [Link]
    FROM OPENQUERY(CubeLinkedServer,
      'SELECT [CATALOG_NAME], [CUBE_NAME]
         , [LEVEL_CAPTION], [DESCRIPTION],
         , [DIMENSION_UNIQUE_NAME]
       FROM $SYSTEM.MDSCHEMA_LEVELS
       WHERE [LEVEL_NUMBER]&gt;0
         AND [LEVEL_IS_VISIBLE]')
    WHERE  (CAST([CATALOG_NAME] AS VARCHAR(255))
         = @Catalog OR @Catalog IS NULL)
      AND (CAST([CUBE_NAME] AS VARCHAR(255))
         = @Cube OR @Cube IS NULL)
      AND LEFT(CAST(CUBE_NAME AS VARCHAR(255)),1)
         &lt;&gt;'$' --Filter out dimensions not in a cube

    UNION ALL

   --Measure Groups
    SELECT CAST('Measure Group' AS VARCHAR(20))   AS [Type]
      , CAST(CATALOG_NAME AS VARCHAR(255))     AS [Catalog]
      , CAST(CUBE_NAME AS VARCHAR(255))           AS [Cube]
      , CAST(MEASUREGROUP_NAME AS VARCHAR(255))   AS [Name]
      , CAST(DESCRIPTION AS VARCHAR(4000)) AS [Description]
      , CAST(MEASUREGROUP_NAME AS VARCHAR(255))   AS [Link]
    FROM OPENQUERY(CubeLinkedServer,
       'SELECT [CATALOG_NAME], [CUBE_NAME]
          , [MEASUREGROUP_NAME],
          , [DESCRIPTION]
        FROM $SYSTEM.MDSCHEMA_MEASUREGROUPS')
    WHERE  (CAST([CATALOG_NAME] AS VARCHAR(255))
       = @Catalog OR @Catalog IS NULL)
     AND (CAST([CUBE_NAME] AS VARCHAR(255))
       = @Cube OR @Cube IS NULL)
     AND LEFT(CAST(CUBE_NAME AS VARCHAR(255)),1)
       &lt;&gt;'$' --Filter out dimensions not in a cube

    UNION ALL

   --Measures
    SELECT CAST('Measure' AS VARCHAR(20))         AS [Type]
      , CAST(CATALOG_NAME AS VARCHAR(255))     AS [Catalog]
      , CAST(CUBE_NAME AS VARCHAR(255))           AS [Cube]
      , CAST(MEASURE_NAME AS VARCHAR(255))        AS [Name]
      , CAST(DESCRIPTION AS VARCHAR(4000)) AS [Description]
      , CAST(MEASUREGROUP_NAME AS VARCHAR(255))   AS [Link]
    FROM OPENQUERY(CubeLinkedServer,
      'SELECT [CATALOG_NAME], [CUBE_NAME]
         , [MEASURE_NAME], [DESCRIPTION],
         , [MEASUREGROUP_NAME]
       FROM $SYSTEM.MDSCHEMA_MEASURES
          WHERE [MEASURE_IS_VISIBLE]')
    WHERE  (CAST([CATALOG_NAME] AS VARCHAR(255))
          = @Catalog OR @Catalog IS NULL)
      AND (CAST([CUBE_NAME] AS VARCHAR(255))
          = @Cube OR @Cube IS NULL)
      AND LEFT(CAST(CUBE_NAME AS VARCHAR(255)),1)
          &lt;&gt;'$' --Filter out dimensions not in a cube

    )
    SELECT *
    FROM MetaData
    WHERE @Search&lt;&gt;''
        AND ([Name] LIKE '%' + @Search + '%'
          OR [Description] LIKE '%' + @Search + '%'
        )
GO
</pre>
<p>We can now use these procs to form the basis of a number of SSRS reports which will dynamically query the DMVs to generate the SSAS cube documentation. I&#8217;ll be covering this stage in my next post.</p>
<ul>
<li>Part 1 &#8211; Creating the DMV stored procs</li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-2/">Part 2 &#8211; Create the SSRS reports</a></li>
<li><a href="/blog/2010/09/olap-cube-documentation-in-ssrs-part-3/">Part 3 &#8211; Use spatial data and maps to create a star schema view</a></li>
<li><a onclick="pageTracker._trackPageview('/downloads/CubeDoc.zip'); " href="/download/blog/CubeDoc.zip" target="_self">Download Source Code</a></li>
</ul>
<p> </p>
<p>News Flash: <a href="http://PurpleFrog.tumblr.com" target="_blank">Purple Frog now has a Tumblr page</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2010/09/olap-cube-documentation-in-ssrs-part-1/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Calculate Run Rate (Full Year Projection) in MDX</title>
		<link>http://www.purplefrogsystems.com/blog/2010/08/calculate-run-rate-full-year-projection-in-mdx/</link>
		<comments>http://www.purplefrogsystems.com/blog/2010/08/calculate-run-rate-full-year-projection-in-mdx/#comments</comments>
		<pubDate>Wed, 18 Aug 2010 17:26:18 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Calculated Member]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[Finance]]></category>
		<category><![CDATA[MDX]]></category>
		<category><![CDATA[OLAP]]></category>
		<category><![CDATA[SSAS]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=152</guid>
		<description><![CDATA[This post explains how to create an MDX calculated member that will take a value from the cube and project it forward to the end of the year. This provides a simple mechanism for calculating what your expected total will be at year end, based upon current performance. To do this more accurately you should [...]]]></description>
			<content:encoded><![CDATA[<p>This post explains how to create an MDX calculated member that will take a value from the cube and project it forward to the end of the year. This provides a simple mechanism for calculating what your expected total will be at year end, based upon current performance.</p>
<p>To do this more accurately you should use time series data mining models in SSAS and use DMX expressions to query the results, but this method is very simple and requires little effort, and will be pretty accurate so long as the data you&#8217;re modelling is fairly linear. Please note though that the more cyclical and seasonal your data is the less effective this will be.</p>
<p>The basic idea is that we take what we have done so far (i.e. year to date sales), look at how far through the year we are, and extrapolate the value of future months (or days/weeks/etc.) based upon values so far.</p>
<p>i.e. If we&#8217;re at March month end and we&#8217;ve sold 100 widgets so far this year, we&#8217;re 1/4 of the way through the year so we multiply 100 by 4 and get a prejected yearly total of 400.</p>
<p><img src="/images/blog/RunRateAt6Months.png" alt="" title="RunRateAt6Months" width="462" height="220" class="alignleft size-full wp-image-153" /><br />
This chart shows the concept of what we&#8217;re doing, and shows the full year prejections calculated in March (with 3 months of available data) and June (6 months of data). The projections obviously get more accurate the further you are through the year.</p>
<p>One of the points to note is that when creating a calculation like this, based upon a time dimension, the calculation should always work with any level of the dimension hierarchy selected. i.e. The user shouldn&#8217;t care whether they&#8217;re looking at a month, week, quarter or a day, the calculation should always work the same. To achieve this we simply use the .currentmember of the time hierarchy.</p>
<p>The following examples are based upon projecting the Internet Sales Amount measure found within the SQL Server 2008 Adventure Works DW sample cube.</p>
<p><b>Step 1 &#8211; What are our total sales so far this year?</b></p>
<p>MDX helpfully provides us with the YTD function which takes care of this for us.</p>
<pre><span style="color: #008000;">
  MEMBER [Measures].[YTD Sales] AS
    AGGREGATE(
      YTD([Date].[Calendar].CurrentMember)
      ,[Measures].[Internet Sales Amount])
</span></pre>
<p>This takes the current member of the Calendar hierarchy, and creates a set of all dates before it (this year) using YTD. It then aggregates (in this case sums) the Internet Sales Amount for all of these dates to calculate YTD Sales.</p>
<p><b>Step 2 &#8211; Which period are we in?</b></p>
<p>Here we&#8217;ll use the same YTD function to create a set of all dates so far this year, but in this case we&#8217;ll count the number of resulting members. Note that because we&#8217;re using the .CurrentMember of the hierarchy, it doesn&#8217;t matter if we&#8217;re looking at a date, week or month, the MDX will work. i.e. If we&#8217;re looking at 21 Jan it will return 21. If we&#8217;re looking at Q3 it will return 3, August will return 8 etc.</p>
<pre><span style="color: #008000;">
  MEMBER [Measures].[CurPeriod] AS
    COUNT(
      YTD([Date].[Calendar].CurrentMember)
      ,INCLUDEEMPTY)
</span></pre>
<p><b>Step 3 &#8211; How many periods are in the year?</b></p>
<p>If we coded this to only work with months then we could hard code this to 12 however we need to keep it generic to all levels of the hierarchy. So, we have to count all the cousins of the current time member [within this year]. Unfortunately there isn&#8217;t a Cousins function in MDX, and Siblings will only return other members within the same parent. i.e. siblings of May 4th would include May 1 through to May 31. To get around this we find the year of the current member by using the Ancestor function.</p>
<pre><span style="color: #008000;">
  ANCESTOR([Date].[Calendar].CurrentMember
  , [Date].[Calendar].[Calendar Year])
</span></pre>
<p>Then we find all of the descendants of the year, at the same level of the hierarchy (week/day/etc.) as the current member. We can then take a count as before.</p>
<pre><span style="color: #008000;">
  MEMBER [Measures].[TotalPeriods] AS
    COUNT(
      DESCENDANTS(
        ANCESTOR([Date].[Calendar].CurrentMember
          ,[Date].[Calendar].[Calendar Year])
        ,[Date].[Calendar].CurrentMember.level)
      ,INCLUDEEMPTY)
</span></pre>
<p><b>Step 4 &#8211; Calculate the Run Rate</b></p>
<p>Calculating the prejected yearly total (run rate) is then a simple calculation</p>
<pre><span style="color: #008000;">
  MEMBER [Measures].[Full Year Run Rate] AS
    [Measures].[YTD Sales]
    * ([Measures].[TotalPeriods]
       /[Measures].[CurPeriod])
</span></pre>
<p>You can then put the whole lot together and see the results&#8230;</p>
<pre><span style="color: #008000;">
WITH

  MEMBER [Measures].[YTD Sales] AS
    AGGREGATE(
      YTD([Date].[Calendar].CurrentMember)
      ,[Measures].[Internet Sales Amount])

  MEMBER [Measures].[CurPeriod] AS
    COUNT(
      YTD([Date].[Calendar].CurrentMember)
      ,INCLUDEEMPTY)

  MEMBER [Measures].[TotalPeriods] AS
    COUNT(
      DESCENDANTS(
        ANCESTOR([Date].[Calendar].CurrentMember
          ,[Date].[Calendar].[Calendar Year])
        ,[Date].[Calendar].CurrentMember.level)
      ,INCLUDEEMPTY)

  MEMBER [Measures].[Full Year Run Rate] AS
    [Measures].[YTD Sales]
    * ([Measures].[TotalPeriods]
       /[Measures].[CurPeriod])

SELECT
{
     [Measures].[Internet Sales Amount]
    ,[Measures].[YTD Sales]
    ,[Measures].[Full Year Run Rate]
    ,[Measures].[CurPeriod]
    ,[Measures].[TotalPeriods]
} ON 0,
{
    DESCENDANTS([Date].[Calendar].[CY 2003])
} ON 1
FROM [Direct Sales]
</span></pre>
<p>In my next blog I&#8217;ll be diong the same calculation in DAX for use with PowerPivot, stay tuned&#8230;</p>
<p>Frog-Blog Out</p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2010/08/calculate-run-rate-full-year-projection-in-mdx/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Excel Cube Pivot drillthrough limited to 1000 rows</title>
		<link>http://www.purplefrogsystems.com/blog/2010/03/excel-cube-pivot-drillthrough-limited-to-1000-rows/</link>
		<comments>http://www.purplefrogsystems.com/blog/2010/03/excel-cube-pivot-drillthrough-limited-to-1000-rows/#comments</comments>
		<pubDate>Thu, 25 Mar 2010 09:17:45 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[Drillthrough]]></category>
		<category><![CDATA[Excel]]></category>
		<category><![CDATA[Pivot]]></category>
		<category><![CDATA[SSAS]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=95</guid>
		<description><![CDATA[When browsing a cube using Excel 2007, you can drillthrough the measures to display up to 1000 rows of the transaction level source data. I often get asked whether this limit of 1000 rows is configurable &#8211; well the good news is yes it is. There is an option in the actions tab of the [...]]]></description>
			<content:encoded><![CDATA[<p>When browsing a cube using Excel 2007, you can drillthrough the measures to display up to 1000 rows of the transaction level source data.</p>
<p>I often get asked whether this limit of 1000 rows is configurable &#8211; well the good news is yes it is.</p>
<p>There is an option in the actions tab of the BIDS cube designer which allows you to specify the maximum rows, but helpfully this is ignored by Excel. Instead, you have to set it in Excel when you create a pivot.</p>
<p>Just click &#8220;<strong>Options</strong>&#8221; on the &#8220;<strong>PivotTable Tools</strong>&#8221; ribon, then in the &#8220;<strong>Change Data Source</strong>&#8221; dropdown click on &#8220;<strong>Connection Properties</strong>&#8220;. In this screen, just change the &#8220;<strong>Maximum number of records to retrieve</strong>&#8221; property.</p>
<p><a href="/images/blog/excelpivotdrillthrough.png"><img class="aligncenter size-full wp-image-96" title="excelpivotdrillthrough" src="/images/blog/excelpivotdrillthrough.png" alt="Excel 2007 Pivot Options" width="458" height="662" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2010/03/excel-cube-pivot-drillthrough-limited-to-1000-rows/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>MDX Sub select Vs WHERE clause</title>
		<link>http://www.purplefrogsystems.com/blog/2010/03/mdx-sub-select-vs-where-clause/</link>
		<comments>http://www.purplefrogsystems.com/blog/2010/03/mdx-sub-select-vs-where-clause/#comments</comments>
		<pubDate>Tue, 02 Mar 2010 09:23:15 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[MDX]]></category>
		<category><![CDATA[WHERE]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=36</guid>
		<description><![CDATA[I&#8217;ve just read an interesting thread on the SQL Server Developer Center forum, regarding how to filter results. Specifically the difference in MDX between using a subselect SELECT x on COLUMNS, y on ROWS FROM ( SELECT z on COLUMNS FROM cube)) or using a where clause SELECT x on COLUMNS, y on ROWS FROM [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve just read an interesting thread on the SQL Server Developer Center forum, regarding how to filter results. Specifically the difference in MDX between using a subselect</p>
<pre>
<p class="code">SELECT x on COLUMNS, y on ROWS
FROM ( SELECT z on COLUMNS FROM cube))

</pre>
<p>or using a where clause</p>
<pre>
<p class="code">SELECT x on COLUMNS, y on ROWS
FROM cube
WHERE z

</pre>
<p>In a simple query they produce the same results, but what is the actual difference? You can read the full thread <a href="http://social.msdn.microsoft.com/Forums/en-US/sqlanalysisservices/thread/3f0a2aba-93de-4f67-8402-181798a6225f" target="_blank">here</a>, but to summarise Darren Gosbell&#8217;s response&#8230;</p>
<p>Using the WHERE clause sets the query context and consequently the CurrentMember. This then enables functions such as YTD and PerdiodsToDate to work.</p>
<p>Using a subselect can provide improved performance, but does not set the context.</p>
<p>Simples..!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2010/03/mdx-sub-select-vs-where-clause/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Scope Problems with MDX Calculated Members</title>
		<link>http://www.purplefrogsystems.com/blog/2009/11/scope-problems-with-mdx-calculated-members/</link>
		<comments>http://www.purplefrogsystems.com/blog/2009/11/scope-problems-with-mdx-calculated-members/#comments</comments>
		<pubDate>Wed, 18 Nov 2009 13:02:34 +0000</pubDate>
		<dc:creator>Alex</dc:creator>
				<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Calculated Member]]></category>
		<category><![CDATA[Cube]]></category>
		<category><![CDATA[MDX]]></category>
		<category><![CDATA[Scope]]></category>

		<guid isPermaLink="false">http://www.purplefrogsystems.com/blog/?p=21</guid>
		<description><![CDATA[We were recently investigating a problem for a client regarding the use of Scope within MDX calculated members. The code in question was similar to this: CREATE MEMBER CURRENTCUBE.[Measures].[Test Measure To Date] AS "NA", VISIBLE = 1; Scope([Date].[Calendar].MEMBERS); [Measures].[Test Measure To Date] = SUM(NULL:[Date].[Calendar].CurrentMember, [Measures].[Test Measure]); End Scope; Scope([Date].[Fiscal].MEMBERS); [Measures].[Test Measure To Date] = SUM(NULL:[Date].[Fiscal].CurrentMember, [...]]]></description>
			<content:encoded><![CDATA[<p>We were recently investigating a problem for a client regarding the use of Scope within MDX calculated members. The code in question was similar to this:</p>
<pre><span style="color: #008000;">CREATE MEMBER
   CURRENTCUBE.[Measures].[Test Measure To Date]
   AS "NA", VISIBLE = 1;
Scope([Date].[Calendar].MEMBERS);
    [Measures].[Test Measure To Date] =
      SUM(NULL:[Date].[Calendar].CurrentMember,
        [Measures].[Test Measure]);
End Scope;
Scope([Date].[Fiscal].MEMBERS);
    [Measures].[Test Measure To Date] =
      SUM(NULL:[Date].[Fiscal].CurrentMember,
        [Measures].[Test Measure]);
End Scope;</pre>
<p></span></p>
<p>Essentially the warehouse was providing a transaction table with credits and debits, this calculated measure was supposed to provide the current balance, summing all transactions to date (not just the current year/period etc, but the entire history). Scope is used to enable the calculation to work across two different date hierarchies, calendar and fiscal.</p>
<p>The problem was that even when the [Date].[Calendar] hierarchy was selected, the code still used the fiscal hierarchy to calculate the value.</p>
<p>This is caused by the fact that [Date].[Fiscal].MEMBERS includes the member [Date].[Fiscal].[All]. Consequently, even when the Fiscal hierarchy was not included in the query, its [All] member was effectively still within the scope. Thus the fiscal calculation was overriding the calendar calculation no matter what was selected.</p>
<p>The solution to this is to exclude [All] from the scope, which can be done by changing the code to the following:</p>
<pre><span style="color: #008000;">CREATE MEMBER
   CURRENTCUBE.[Measures].[Test Measure To Date]
   AS "NA", VISIBLE = 1;
Scope(DESCENDANTS([Date].[Calendar],,AFTER));
    [Measures].[Test Measure To Date] =
      SUM(NULL:[Date].[Calendar].CurrentMember,
        [Measures].[Test Measure]);
End Scope;
Scope(DESCENDANTS([Date].[Fiscal],,AFTER));
    [Measures].[Test Measure To Date] =
      SUM(NULL:[Date].[Fiscal].CurrentMember,
        [Measures].[Test Measure]);
End Scope;</pre>
<p></span></p>
<p>DESCENDANTS(xxx,,AFTER) is a simple way of identifying every descendent of the hierarchy AFTER the current member, which is [All] when not specified.</p>
<p>Problem solved, Frog-blog out.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.purplefrogsystems.com/blog/2009/11/scope-problems-with-mdx-calculated-members/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>

