When developing SQL Server Reporting Services (SSRS) reports, BIDS caches the query results when you preview the report. This cache is then used next time you run a preview. This has the benefit of speeding up report development, but it does cause a problem when you want to test changing data.
A simple way of forcing the cache to refresh is to open the folder containing the .rdl report files, and delete the corresponding .rdl.data files. The next time you preview the report SSRS will be forced to requery the source.
To save time, I use the following macro to take care of it.
Press ALT+F8 to open the Macro Explorer, and add a new module called “RemoveRDLDataFiles” under MyMacros. Edit the file and add the following code to the file. (This is for SQL Server 2008, you may need to tweak the references for 2005).
Imports System Imports EnvDTE Imports EnvDTE80 Imports EnvDTE90 mports System.Diagnostics Imports System.IO Public Module RemoveRDLDataFiles Sub RemoveRDLDataFiles() Dim project As Project Dim Folder As String project = DTE.ActiveSolutionProjects(0) Dim fi As New FileInfo(project.FullName.ToString) Folder = fi.DirectoryName For Each FileFound As String In Directory.GetFiles(Folder, "*.rdl.data") File.Delete(FileFound) Next End Sub End Module
You can then run it by either double clicking on the macro, or assigning a keyboard shortcut to it (via Tools, Customize, Keyboard).
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 – well the good news is yes it is.
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.
Just click “Options” on the “PivotTable Tools” ribon, then in the “Change Data Source” dropdown click on “Connection Properties“. In this screen, just change the “Maximum number of records to retrieve” property.
Whilst designing a data warehouse for a banking client recently, I needed to calculate projected future loan payments (including breaking this down by interest and capital payments) for every customer throughout the life of the loan.
In Excel this is a pretty simple job, as Microsoft helpfully provide a number of functions to do just that (namely PMT, FV, IPMT and PPMT). In SQL Server however we do not have the luxury of having ready made functions, so I set about making my own.
Initially I coded them up as SQL Server functions, only to find that the internal rounding that SQL performs renders the results too inacurate to be usable. It was therefore necessary to write the functions in a C#, and wrap them up in a CLR library. SQL Server can then import them as scalar functions, to be used by any query that requires them.
To overcome this, the Purple Frog team have written a .Net CLR library which add four loan amortisation functions to SQL Server, which can be called from within a query as scalar functions, such as:
SELECT dbo.PMT(@APR/12.0, @Term, @LoanValue, 0, 0)
The functions provided are:
- PMT (The monthly payment of a loan)
- FV (The future value of a loan at a given month)
- IPMT (The interest portion of the monthly payment at a given month)
- PPMT (The capital portion of the monthly payment at a given month)
These are designed to mirror the parameters and results of the Excel functions, and have been written in C# using Visual Studio 2008, and tested against SQL Server 2008 Enterprise.
The functions perform surprisingly well; in tests I was able to calculate over 3 million monthly payments per minute, and that was on a relatively underpowered development server.
I must thank Kevin/MWVisa1 for writing a superb article explaining the finer points of the calculation process in his post here, on which the bulk of this code is derived.
You can download the C# code, or the pre-compiled binary from the Frog-Blog download section.
For anyone interested in SQL Server, the unmissable SQL Bits conference is returning for round six. “SQL Bits – The 6th Sets” is being held in central London for the first time, and unlike previous conferences it will be for one day only on Friday 16th April 2010.
It’s being held as part of the SQL Server 2008 R2 release, also happening that week in London.
Registration is now open, you can find out more at the SQL Bits website.
We highly recommend the event, hope to see you there…
I’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 cube WHERE z
In a simple query they produce the same results, but what is the actual difference? You can read the full thread here, but to summarise Darren Gosbell’s response…
Using the WHERE clause sets the query context and consequently the CurrentMember. This then enables functions such as YTD and PerdiodsToDate to work.
Using a subselect can provide improved performance, but does not set the context.
Simples..!
Purple Frog is presenting a session on data warehouse design concepts at the South Wales SQL Server User Group on Thursday 25th February 2010.
If you’re in the area and want to come along you can register for free here. Eversheds are hosting the event in their Cardiff office.
Adam Morton will be demonstrating an ETL control framework in SSIS, and Alex Whittles will be discussing the concepts of data warehousing and the fundamental differences in architecture between OLTP and OLAP systems.
Hope to see you there!
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,
[Measures].[Test Measure]);
End Scope;
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.
The problem was that even when the [Date].[Calendar] hierarchy was selected, the code still used the fiscal hierarchy to calculate the value.
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.
The solution to this is to exclude [All] from the scope, which can be done by changing the code to the following:
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;
DESCENDANTS(xxx,,AFTER) is a simple way of identifying every descendent of the hierarchy AFTER the current member, which is [All] when not specified.
Problem solved, Frog-blog out.
I was working on a new SSAS 2008 cube today, and came across an error which Google was unable to help with. I thought I’d post the solution here to help anyone else who may encounter it.
The cube in question will be primarily be accessed using Excel 2007, so I’d been dutifully testing it along the way to ensure all was well. And then, after a number of changes the following error appeared when connecting to the cube from Excel to create a pivot table.
Excel was unable to get necessary information about this cube. The cube might have been reorganized or changed on the server.
Contact the OLAP cube administrator and, if necessary, set up a new data source to connect to the cube
Connecting and querying the cube via SSMS or BIDS worked without error (hense I didn’t spot the error sooner!).
A quick Google revealed a number of posts regarding this error, but they all related to attributes containing invalid characters when accessed from Excel 2000 Or problems with translations and locale settings in the .oqy file. Neither of these was the cause here, so I had to go back and recreate every change I had made step by step to track the problem.
Well, I’m please to report that in the end it was nothing more that a simple spelling mistake in a named set. One of the dynamic named sets in the cube calculations referred to a specific member of a dimension, which was spelled slightly incorrectly. (Simplified example..)
CREATE DYNAMIC SET CURRENTCUBE.[Set1]
AS {[Dimension].[Attribute].[Value1],
[Dimension].[Attribute].[Value2WithTypo]
};
When querying calculated measures through MDX in SSMS, the MDX parser just ignored the problem and only uses the valid members, however it appears as though Excel 2007 is slightly more picky with its cubes.
Useful to know, and even more useful when used as a tool to double check for any errors in the MDX calculations.
Firstly sorry for not posting anything for a while, I can only blame spending too much time working and on my uni assignments – not much time free these days!
I stumbled upon a great post by Barry King, demonstrating a way of applying database driven style configuration to a set of reporting services reports, in the absence of any style sheet support in SSRS. It doesn’t rely on custom code (most people’s favourite way of implementing style configuration), but on a single dataset in the report.
Well worth a read if you have to keep a large set of reports looking consistent, especially when your users constantly require changes to the style. (As if they would do that?!)
Thanks to Barry for sharing the details: http://www.simple-talk.com/sql/sql-tools/reporting-services-with-style/
After a couple of days off work last week with SQL Bits III and SQL 2008 Unleashed, it’s back to the grindstone this week; however before I get onto the serious stuff I’d like to say thank you to the organisers of both events. Watching you on stage headbanging to Rockstar – legendary! (You can see for yourself here and here…).
Anyway, back to MDX…
This post explains how you can build a dynamic MDX query in Reportins Services, customised to the users requirements. This can often bring some quite major performance benefits.
Lets say for example that you want to have a sales report grouped dynamically by either product, sales person, department or customer. Normally you would use a single static MDX query, and then add a dynamic grouping to the table in the report. This is fine, until you try it on a large dataset. If you only have 50 products, 2 salesmen, 5 departments and 100 customers, your MDX needs to return 50,000 records, the report then has to summarise all of this into the level of grouping you want. This renders the pre-calculated aggregations in OLAP pretty much worthless.
To get around this, you can generate your MDX dynamically, so that the query returns the data already grouped into the correct level. You can also use this to add extra filters to the query, but only when they are required.
To start with, lets see how we would do this normally with SQL. Assuming we’re working from a denormalised table such as this
![]() |
Dynamic SQL is pretty simple, instead of having your dataset query as
SELECT SalesPerson,
Sum(Sales) AS Sales
FROM tblData
GROUP BY SalesPerson
you can add a report parameter called GroupBy,
![]() |
and then use an expression as your dataset
="SELECT "
+ Parameters!GroupBy.Value + " AS GroupName,
Sum(Sales) AS Sales
FROM tblData
GROUP BY " + Parameters!GroupBy.Value
However MDX queries don’t let you use an expression in the dataset, so we have to work around that quite major limitation. To do this we make use of the OpenRowset command. You need to enable it in the surface area config tool, but once it’s enabled you can fire off an OpenRowset command to SQL Server, which will then pass it on to the cube. As the datasource connnection is to SQL Server not Analysis Services, it allows you to use an expression in the dataset.
="SELECT * FROM OpenRowset('MSOLAP',
'DATA SOURCE=localhost; Initial Catalog=SalesTest;',
'SELECT
{[Measures].[Sales]} ON 0,
NON EMPTY {[Product].[Product].[Product].Members} ON 1
FROM Sales') "
You can then expand this to make it dynamic depending on the value of a parameter. Before we do this though, there are a couple of items I should point out.
1) As the expression can get quite large, I find it much easier to create the query from a custom code function
2) As SSRS can’t interpret the expression at runtime, you need to define the fields in your dataset manually (more on this later)
To use a custom code function, just change the dataset expression to
=Code.CreateMDX(Parameters)
We pass in the parameters collection so that we can use the parameters to determine the query. Create a function called CreateMDX() in the code block
![]() |
You can then construct your MDX query within the code block.
Public Function CreateMDX(ByVal params as Parameters) as string
Dim mdx as string
mdx = "SELECT * FROM OpenRowset("
mdx += " 'MSOLAP', "
mdx += " 'DATA SOURCE=localhost; Initial Catalog=SalesTest;', "
mdx += " ' SELECT {[Measures].[Sales]} ON 0, "
mdx += " NON EMPTY {[Product].[Product].[Product].Members} ON 1 "
mdx += " FROM Sales ' "
mdx += ")"
return mdx
End Function
We’re almost there…
The next problem is that the field names returned by the query are less than helpful. To fix this we just need to alias the fields in the query. I usually take the opportunity of casting the numerical fields so that the report treats them as such, rather than as a string.
Public Function CreateMDX(ByVal params as Parameters)
as string
Dim mdx as string
mdx = "SELECT "
mdx += " ""[Product].[Product].[Product].[MEMBER_CAPTION]"" AS GroupName, "
mdx += " Cast(""[Measures].[Sales]"" AS int) AS Sales "
mdx += " FROM OpenRowset("
mdx += " 'MSOLAP', "
mdx += " 'DATA SOURCE=localhost; Initial Catalog=SalesTest;', "
mdx += " ' SELECT {[Measures].[Sales]} ON 0, "
mdx += " NON EMPTY {[Product].[Product].[Product].Members} ON 1 "
mdx += " FROM Sales ' "
mdx += ")"
return mdx
End Function
(please do watch out for the quotes, double quotes and double double quotes, it can get a little confusing!)
We then need to tell the dataset which fields to expect from the query.
![]() |
You can now use the dataset in your report.
However, the original point of this was to make the query dynamic… All we need to do to achieve this is expand the VB.Net code accordingly.
Public Function CreateMDX(ByVal params as Parameters) as string
Dim mdx as string
mdx = "SELECT "
IF params("GroupBy").Value.ToString()="Product" THEN
mdx += " ""[Product].[Product].[Product]"
ELSE IF params("GroupBy").Value.ToString()="SalesPerson" THEN
mdx += " ""[Sales Person].[Sales Person].[Sales Person]"
ELSE IF params("GroupBy").Value.ToString()="Customer" THEN
mdx += " ""[Customer].[Customer].[Customer]"
END IF
mdx += ".[MEMBER_CAPTION]"" AS GroupName, "
mdx += " Cast(""[Measures].[Sales]"" AS int) AS Sales "
mdx += " FROM OpenRowset("
mdx += " 'MSOLAP', "
mdx += " 'DATA SOURCE=localhost; Initial Catalog=SalesTest;', "
mdx += " ' SELECT {[Measures].[Sales]} ON 0, "
IF params("GroupBy").Value.ToString()="Product" THEN
mdx += " NON EMPTY {[Product].[Product].[Product]"
ELSE IF params("GroupBy").Value.ToString()="SalesPerson" THEN
mdx += " NON EMPTY {[Sales Person].[Sales Person].[Sales Person]"
ELSE IF params("GroupBy").Value.ToString()="Customer" THEN
mdx += " NON EMPTY {[Customer].[Customer].[Customer]"
END IF
mdx += ".Members} ON 1 "
mdx += " FROM Sales ' "
mdx += ")"
return mdx
End Function
It’s certainly not that simple, and debugging can cause a few headaches, but you can benefit from a massive performance in complex reports if you’re prepared to put the work in.
![]() |
You can download the project files here
As always, please let me know how you get on with it, and shout if you have any queries…
Alex








I specialise in designing and implementing SQL Server business intelligence solutions,
and this is my blog! Just a collection of thoughts, techniques and ramblings on SQL Server, Cubes, Data Warehouses, MDX, DAX and whatever else comes to mind.
