Category: SSRS

SQL Server Reporting Services related musings

Technical Article: Report Model (SMDL) Alternatives in SQL Server 2012

It is with tremendous joy that I announce the release of my very first Technical Article! Click the image below to get a copy of your very own.

image

When I first started in the SQL Server business with Digineer about 6.5 years ago, one of my earliest projects was implementing a SQL Server 2005 Report Model for a client. Lara Rubbelke (Blog|Twitter), who led Digineer’s Information Management (SQL Server) Team at the time, set me up with that project along with some top cover from the mighty Jason Strate (Blog|Twitter). With two great mentors like I was lucky enough to have, that project set the stage for my career in SQL Server. It was during that project that I came to firmly believe in empowering end users in a managed environment. In the next few years, I ended up working on several Report Models for clients and giving well attended Intro To Report Model presentations.

Upon discovering that SQL Server 2012 meant the beginning of the end for Report Models, I was concerned for the folks that had been investing in them since their release. I saw forum posts that expressed frustration at their demise since many organizations had come to rely on them. It was clear that there were some technologies available that were superior to Report Models that could make great replacements in different scenarios. I looked for a source that combined all that information into a single location to help ease the transition of Report Models to other alternatives in the Microsoft BI stack. Since I could not find one, I decided I should go ahead and make one. Behold!

I want to thank Lara and Jason for their mentorship at the beginning of my career and through today. I also want to thank my great Technical Reviewers for this article:

Jin Cho (LinkedIn) – Microsoft

Mike Donnelly (Blog|Twitter) – Digineer

Aaron Drinkwine – Digineer

Chris Fish (Twitter) – Digineer

I need to thank Digineer for their continued support and being an awesome place to call home.

SSRS Cascading Multi-Value Parameters

Not too long ago, there was a question posted to #ssrshelp on Twitter regarding having multi-value parameters in SSRS 2005 that ALSO cascade. While I don’t have access to a SQL 2005 instance anymore, I wanted to try it in 2012 since I had an idea how to accomplish it. My idea worked just fine in 2012 and uses functionality that, if I recall correctly, should be available in 2005 as well.

To start, I want to define these two types of parameters so we are all on the same page.

Multi-Value parameters allow the user to select one, some, or ALL values from a drop-down list. This is native SSRS functionality and it creates the Select ALL option for you. The result(s) of the user selection are placed into a comma separated list of the values chosen, which you can then pass to a dataset or use in some other way.

Cascading parameters allow the value(s) select by one parameter (the Parent, if you will) to control the available values presented in another parameter (the Child, if you will). An example would be a list of US States in one parameter and a list of Counties in the second. The Counties parameter would limit its available values to the Counties that are in the State(s) chosen in the State parameter. With Cascading parameters, the Child parameter is not activated until a valid choice has been made on the Parent parameter.

I pretty much always use stored procedures for my datasets with reporting. Now, natively, passing multi-value parameters to a stored procedure is not supported. I know of two ways around that, both of which I have used at clients.

The XML Method

I wrote about this method myself in this post.

The Table-Value Function Method

This one is demonstrated in a great post by David Leibowitz (Blog|Twitter) here.

I will be using the Table-Value Function method in this post.

We start by creating a quick database and a few objects. Feel free to use an existing database to house these objects instead.

USE master;
GO
 
CREATE DATABASE SSRSParmDemo
;
GO
 
USE SSRSParmDemo;
GO
 
CREATE TABLE dbo.Race
(
      RaceID int IDENTITY(1,1) NOT NULL
    , RaceName varchar(50) NOT NULL
    , CONSTRAINT PK_Race PRIMARY KEY CLUSTERED (RaceID)
)
;
GO
 
INSERT INTO dbo.Race VALUES
  ('Dwarves')
, ('Elves')
, ('Men')
;
GO
 
CREATE TABLE dbo.Person
(
      PersonID int IDENTITY(1,1) NOT NULL
    , RaceID int NOT NULL
    , PersonName varchar(100) NOT NULL
    , CONSTRAINT PK_Person PRIMARY KEY CLUSTERED (PersonID)
    , CONSTRAINT FK_Person_Race FOREIGN KEY (RaceID) REFERENCES dbo.Race (RaceID)
);
GO
 
INSERT INTO dbo.Person VALUES
  (1, 'Balin')
, (1, 'Gloin')
, (1, 'Thorin')
, (2, 'Elrond')
, (2, 'Legolas')
, (2, 'Buddy?')
, (3, 'Aragorn')
, (3, 'Boromir')
, (3, 'Faramir')
;
GO
 
    
/**
-- http://blog.summitcloud.com/2010/01/multivalue-parameters-with-stored-procedures-in-ssrs-sql/
**/
 
CREATE FUNCTION dbo.fn_String_To_Table (
   @String VARCHAR(max), /* input string */
   @Delimeter char(1),   /* delimiter */
   @TrimSpace bit )      /* kill whitespace? */
RETURNS @Table TABLE ( [Val] VARCHAR(4000) )
AS
BEGIN
    DECLARE @Val    VARCHAR(4000)
    WHILE LEN(@String) > 0
    BEGIN
        SET @Val    = LEFT(@String,
             ISNULL(NULLIF(CHARINDEX(@Delimeter, @String) - 1, -1),
             LEN(@String)))
        SET @String = SUBSTRING(@String,
             ISNULL(NULLIF(CHARINDEX(@Delimeter, @String), 0),
             LEN(@String)) + 1, LEN(@String))
  IF @TrimSpace = 1 Set @Val = LTRIM(RTRIM(@Val))
    INSERT INTO @Table ( [Val] )
        VALUES ( @Val )
    END
    RETURN
END;
GO
 
CREATE PROC dbo.getRace
 
AS
 
SELECT
      RaceID AS Value
    , RaceName AS Label
FROM dbo.Race
;
GO
 
CREATE PROC dbo.getPerson
 
    @RaceList varchar(max)
 
AS
 
SELECT
      p.PersonID AS Value
    , p.PersonName AS Label
FROM dbo.Person p
WHERE p.RaceID IN
    (
        SELECT Val
        FROM dbo.fn_String_To_Table (@RaceList,',',1)
    )
;
GO
 
CREATE PROC dbo.getPersonReport
 
      @PersonList varchar(max)
 
AS
 
SELECT
      p.PersonName + ' is of the Race of ' + r.RaceName AS PersonRace
FROM dbo.Person p
INNER JOIN dbo.Race r
ON p.RaceID = r.RaceID
WHERE p.PersonID IN
    (
        SELECT Val
        FROM dbo.fn_String_To_Table (@PersonList,',',1)
    )
;
GO

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

The purpose of each procedure is as follows:

getRace provides a list of the Races available in the dbo.Race table. This will be the parent parameter whose selected values will cascade as a filter for the Person parameter.

getPerson provides a list of the folks in the dbo.Person table based on the list of Races passed in to its @RaceList parameter. This will be the child parameter whose values will be pre-filtered by those selected in the Race parameter.

getPersonReport returns the folks from the Person table based on the list of PersonID values passed in via the @PersonList parameter. This will be the dataset for the report.

Let’s add the three datasets to the report, each using the Shared Data Source pointing to the database created above. You can see them in Figure 1.

Figure 1

image

Each dataset maps to the stored procedure of the same name.

The report for this example is very basic. It is simply a table (tablix) which shows the PersonRace column for each row returned in the getPersonReport dataset. The report layout is shown in Figure 2.

Figure 2

image

Once all three datasets are added, there will be two report parameters added for you, as shown in Figure 3.

Figure 3

image

We need to do a little work with each parameter.

Double-click the RaceList parameter, revealing the Report Parameter Properties dialog shown in Figure 4.

Figure 4

SNAGHTML23ede7

Ensure that the Allow Multiple Values option is checked, as shown by the red arrow in Figure 4. Then click the Available Values option shown in the oval in the upper left. This will bring up the Available Values dialog just like Figure 5.

Figure 5

SNAGHTML2e6863

Select the Get Value From a Query option and choose the getRace dataset. From there, just match up the Value and Label fields like in Figure 5. Then click OK.

Double-click the Person parameter to bring up its Properties, as in Figure 6.

Figure 6

SNAGHTML2bb1d1

Make sure the Allow Multiple Values option is checked. In my report, the Allow Blank Value for this parameter was checked automatically. I tried running the report both with it checked and with it unchecked and noted no difference in functionality. Click the Available Values option to bring up a dialog similar to Figure 7.

Figure 7

SNAGHTML2ddb03

Check the Get Value From a Query option and set the Dataset to getPerson. Set the Value and Label fields respectively as shown in Figure 7 and click OK.

Now let’s run the report, which should resemble Figure 8.

Figure 8

image

Notice the Race List parameter has checkboxes for each Race along with a (Select All) option, which SSRS built for us. Also notice that the Person List parameter is grayed out. Because it requires Races to be passed to it, SSRS knows that we cannot use until at least one Race has been selected. When we choose a Race, perhaps Dwarves, and click away from the Race List parameter, that value is passed to the getPerson stored procedure, producing the available values list for the Person List parameter, as shown in Figure 9.

Figure 9

image

Notice that we have a list of folks along with the (Select All) option that SSRS created for us. If you are familiar enough with the works of J.R.R. Tolkien, you will recognize that all of these folks are Dwarves. Either way, click (Select All), which will put a check in every checkbox available, and click View Report. Your results will resemble Figure 10.

Figure 10

image

That’s really all there is to it. Play around with different combinations (you may enjoy the Easter Egg among the Elves; What’s your favorite color?).

Since it has been a long time since I used SSRS 2005, I cannot fully promise this will work. But none of the functionality above is all that fancy, so it seems to me it could possibly work just fine. If you try it in 2005, please let me know how it goes.

Dynamic SSRS Chart Data Label Visibility

The other day, there was a question posted on the #ssrshelp hash tag on Twitter. The question was whether SSRS would allow you to show data labels for a chart, but ONLY on the last item in a series. For example, if you have months ranging from January to December, can you show the data label only on December’s value?

You can indeed. Here is how you do it.

Let’s start with a basic data set query (the source does not matter since were are hardcoding everything here.

SELECT     '2011-01-01' AS ValDate, 1 AS Val
UNION ALL
SELECT     '2011-02-01' AS ValDate, 2 AS Val
UNION ALL
SELECT     '2011-03-01' AS ValDate, 1.5 AS Val

 

We’ll call it Dateset1. It returns the following:

image

Let’s see a chart based on this data with the data labels turned on.

image

This is basic stuff. Now, let’s add the necessary items to hide all of the data labels except the one for 2011-03-01.

First, we’ll add a new Report Variable. Click on Report and choose Report Properties.

image

We create a new variable called MaxDateVal. We then click on the Expression button, shown in the rectangle above.

image

Don’t worry about the red underline here. Click OK when you’re done.

Now we head over to the Visible property of the Chart Series data labels, shown below in the rectangle near the bottom.

image

Click on the property value and choose Expression.

image

Enter the following expression.

image

Click OK and then run the report.

image

Notice only the MAX date value, 2011-03-01, has a data label now.

Have fun.

Using Bogus Measures in SSRS Parameter Available Values Lists

I am currently working on a project involving many reports against an Analysis Services 2008 R2 cube that we implemented for the client. The available values lists for the report parameters are largely just lists of members of various dimension attribute hierarchies from the cube. Since we are not going to actually use any measures in our available values lists, we decided it was best if we didn’t even hit any measure groups at all. Why waste resources retrieving facts we don’t even want, right?

Let’s take our example from the Adventure Work DW 2008 R2 SSAS database, which contains the Adventure Works cube.

Suppose we have a report parameter that allows the user to select from the list of Product Categories.

We could start by just putting the Product Categories on the COLUMNS axis, thereby specifying only the items we actually want. Since all queries must include the COLUMNS axis, we have to start there.

-- Query 1
SELECT [Product].[Product Categories].children ON COLUMNS 
FROM [Adventure Works]

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Query 1 produces the following results:

image

With this query, we are hitting a measure group even though we didn’t specify one, in this case the Reseller Sales measure group, to retrieve the default measure, Reseller Sales Amount. Not only that, but we have the Product Categories pivoted and would have to un-pivot them to use them in an available values list. That’s not the best way, for sure.

We could just go ahead and resign ourselves to retrieving a measure and specify the Reseller Sales Amount measure since we’re going to get it anyway.

-- Query 2
SELECT [Measures].[Reseller Sales Amount] ON COLUMNS,
[Product].[Product Categories].children ON ROWS
FROM [Adventure Works]

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Query 2 produces the following results:

image

This is less than spectacular as well. While we actually have our Product Categories on the Rows where we want them, we are still hitting the measure group for no reason. We have already seen that we will get the default measure even if we don’t specify one. So, let’s specify one that isn’t in a measure group at all.

In the following query, we declare a new measure called Bogus that is just a constant, “1” and nothing else.

-- Query 3
WITH MEMBER [Measures].[Bogus] AS "1"

SELECT [Measures].[Bogus] ON COLUMNS,
[Product].[Product Categories].children ON ROWS
FROM [Adventure Works]

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Query 3 produces the following results:

image

This query still puts the Product Categories on the rows where we want them. Hazzah. And, since we specified a Measure, even a Bogus one, we didn’t need to hit any measure groups, not even for the default measure. Double Hazzah.

So, when constructing available values lists, follow the lead of modern political discourse in the United States: Don’t muddy things up with facts. Oh snap!

Announcing the #SSRSHelp Twitter Hashtag

I think it’s clear to anyone with half an eye open that the #SQLHelp hastag has been, and continues to be, a huge boon to the SQL community. It is a fantastic way to get assistance with SQL Server. If I were a DBA, it would be perfect for me.

I just returned from the PASS Summit in Seattle (my customary Top Ten List post is in the works). While there, I had the great pleasure to have lunch with MVP Stacia Misner (Blog | Twitter) and Erika Bakse (Blog | Twitter) at the Birds of a Feather lunch, an event designed to bring folks with similar SQL Server interests together for food, folks, and fun. We discussed how the #sqlhelp requests related to Reporting Services get lost in the volume of the more DBA related topics.

Well, let’s make this better. Let’s all start using the #SSRSHelp hashtag to mark requests for help on Reporting Services related topics. This will make it easier those of us seeking ways to help the community find people to help. It will also make it a much better experience for those seeking help.

We have several bastions of SSRS awesomeness on board who have already started monitoring #SSRSHelp:

MVP Stacia Misner (Blog | Twitter)
MVP Jessica Moss (Blog | Twitter)
Erika Bakse (Blog | Twitter)
Jes Schultz Borland (Blog | Twitter)
Doug Lane (who perhaps had the idea in the first place) (Blog | Twitter)
Angel Abundez (Blog | Twitter)
Chris Randall (Blog | Twitter)
Mark Vaillancourt (my very own self, whose blog you now read with rapt joy | Twitter)

Please feel free to join in and help spread the word. Also, even if you think you will not be able to help, I encourage you to follow anyway and learn from the questions and answers that will flow through the stream. I learn a lot myself that way.

Please keep in mind that the guidelines for #SSRSHelp usage should match that of #SQLHelp. Most important: it is for asking and answering questions about SQL Server Reporting Services. It is not for trying to sell products or trying to funnel folks to your blog or for trying to get others to pool their money with you in the hopes of helping that Nigerian prince who emailed you the other day.

Before I wrap up, I would like to give a quick preview of my PASS Summit Top Ten List:

OMG IT WAS FREAKING AWESOME!

Thanks.

This just in: Our first #SSRSHelp win is in the books. Smile

PASSMN June Meeting: What’s New In SSRS 2008 R2

I have been lax in promoting my local SQL users group, so here goes. Oh, by the way, it’s Lara’s fault that I am a SQL Server consultant. Thanks Lara!

June PASSMN Meeting & Newsletter
Sponsored by Magenic

There will be books, shirts and other swag at the end of the meeting!

Location:    8300 Norman Center Drive, 9th Floor, Bloomington, MN  55437
Date:     June 15th, 2010
Time:    3:00 – 5:30

Please click here for meeting details and to RSVP.

Registration has changed with the move from our previous hosting site and you will be required to log into http://www.sqlpass.org in order to register for our events. If you have any issues with this, please contact support@mnssug.org.

What’s New in SQL Server 2008 R2 Reporting Services

Lara Rubbelke (Twitter | Blog), Microsoft

Amp up your Reporting Services with the many great enhancements in SQL Server 2008 R2. Lara will take you on a tour of all that is new in R2 including collaboration features, new visualizations, rendering enhancements, and new data sources. After this session you will wonder why they call R2 an "interim" release!

Questions for presenters?
If you have any questions you would like our presenter to answer in the meeting, feel free to submit them ahead of time to support@mnssug.org or to any of the board members before the meeting. All question askers will be kept anonymous.

Other News
•       Live Meeting
   o    Beginning in 2010 meeting, you will now be able to join monthly meetings virtually through Live Meeting. This is a great opportunity to stay connected that has been provided to us by PASS.
•       SQL Azure Presentation
   o   Presented by Mike Benkovich at Twin Cities Cloud Computing User Group (TCCCUG)
   o   July 8th, 2010
   o   Click Here for more information
•       PASS Summit 2010
   o    Registration for PASS Summit 2010 is now open. Register for only $1,395 until June 30. Tap into your 2009 or 2010 training budgets to take advantage of the low rate for PASS Summit.

Clarification on Viewing Report Properties in SSRS 2005/2008

I have been to two different presentations on SQL Server 2008 R2 Reporting Services enhancements over the past month or so. In both of those presentations, the presenter said just about these exact words: “Now, you can view a report’s properties without having to run it first.”

I decided to check Microsoft.com to see if official content said the same type of thing. Sure enough, the Introducing Microsoft SQL Server 2008 R2 eBook has the following on page 185:

“Rather than requiring you to open a report first and then navigate to the properties pages,
Report Manager gives you direct access to the report properties from a menu on the report
listing page.“

All three sources are indicating that in versions of SSRS prior to SQL 2008 R2, you have to open/run a report before you can get to its properties in Report Manager. However, not only is it possible to view report properties in 2005 or 2008 Report Manger without running the report first, it is very easy.

Here is my Report Manager for my SQL 2008 instance (2005 is essentially identical).

image

Notice the “Show Details” button in the red rectangle to the right in the image above. Give that a click and your Report Manager will change to the figure below.

image

Click the Properties button shown in the red rectangle to the left of the report in the image above. That will open up the properties of the report. No need to run it first.

Don’t get me wrong, the new Report Manager interface in SQL 2008 R2 is a great improvement with its Sharepoint style drop down lists. I just felt the need to point out that being able to view a report’s properties without running it first is not a “new” feature of SSRS in SQL 2008 R2. You can do it today.

Linked Reports: Getting the Montagues and Capulets To Share

Two households, both alike in dignity,

In fair Verona, where we lay our scene,

From ancient grudge break to new mutiny,

Where civil blood makes civil hands unclean.

          ~ Romeo and Juliet, William Shakespeare

 

Those Montagues and Capulets don’t do so well with the sharing thing. They fight. A lot. This causes a lot of trouble in Verona, often resulting in various Montagues and Capulets winding up in jail.

Let’s suppose that Prince Escalus, essentially the mayor of Verona, decides to commission a report detailing what members of each family have been jailed. Escalus has decreed that an Excel attachment of this report is to be distributed to each of the families via subscription from SQL Server 2008 Reporting Services (Standard Edition). Each report will contain only the information for that family and the file name of the Excel attachment must include the name of the family as well. The definition of each report is identical, merely the records and report name (and thus the Excel attachment) must be personalized for each family. This is based on a solution I implemented for a client a while back. I think this solution is fine when there are not a lot of different Family values. For example, if you have 100 different families, then perhaps creating 100 different linked reports and their subscriptions may not be ideal. In such a scenario, you may want to create an SSIS solution that will perform the exports and emails using package variables for the personalization of each ( have not tried it but it seems like it should work). But for now, I will instead recount the process I used that worked well for the client. If you have solved this a different way, I would love to see it and possibly even shamelessly steal it should I encounter this situation again. 🙂

One could accomplish the report personalization goal by creating two reports, one for each family. This would get the job done. However, from a manageability standpoint, this is not ideal. Suppose there were 20 families involved? Do you really want to maintain 20 all but identical reports, propagating changes to all 20 reports when they are required? Instead, why not create a single report with a Family parameter? That would take care of the manageability issue since you would have only one report definition to maintain. However, this would not solve the personalization goal. This is because the name of an Excel attachment exported from SSRS defaults to the the name of the report itself. For sooth!

Enter Linked Reports! (MSDN How To: Create A Linked Report) This link is for SQL 2008 but 2005 linked reports are essentially the same as far as I have scene. And it was actually in SQL 2005 that I implemented this solution for a client (not Prince Escalus) and it behaved the same in 2008 when I did it for this post.

A linked report is one that is based on the report definition of another report. Changes to the definition of the base report are propagated to all linked reports. But the linked reports have their own name (Aha!) and subscriptions (Aha! again), as well as some other properties. Zounds!

Let’s create our table. I am keeping this in the Adventureworks database for simplicity, but creating my own schema. I added the IF EXISTS for the schema since I am SURE you implement all of the code in my posts and therefore already have a schema named markvsql in your copy of the Adventureworks database. 🙂

USE Adventureworks

GO

 

--Create the markvsql schema if it does not already exist

IF NOT EXISTS

(

    SELECT * FROM sys.schemas WHERE [name] = 'markvsql'

)

EXEC sp_executesql N'CREATE SCHEMA markvsql'

GO

 

CREATE TABLE markvsql.VeronaJailing

(

      PrisonerName varchar(10)

    , Family varchar(10)

)

GO

 

INSERT INTO markvsql.VeronaJailing (PrisonerName, Family) VALUES('Romeo','Montague')

INSERT INTO markvsql.VeronaJailing (PrisonerName, Family) VALUES('Benvolio','Montague')

INSERT INTO markvsql.VeronaJailing (PrisonerName, Family) VALUES('Balthasar','Montague')

INSERT INTO markvsql.VeronaJailing (PrisonerName, Family) VALUES('Juliet','Capulet')

INSERT INTO markvsql.VeronaJailing (PrisonerName, Family) VALUES('Tybalt','Capulet')

INSERT INTO markvsql.VeronaJailing (PrisonerName, Family) VALUES('The Nurse','Capulet')

Now that we have some data, we will create the stored procedure to return our report dataset.

CREATE PROC markvsql.GetVeronaPrisoners

      @Family varchar(10)

AS

SELECT

      vj.PrisonerName

FROM markvsql.VeronaJailing vj

WHERE vj.Family = @Family

GO

Now let’s give our fabulous sproc a spin around the block.

Let’s meet our Capulets <<applause>>:

EXEC markvsql.GetVeronaPrisoners @Family = 'Capulet'

GO

 

/* Capulet RESULTS:

PrisonerName

------------

Juliet

Tybalt

The Nurse

*/

And our Montagues <<applause>>:

EXEC markvsql.GetVeronaPrisoners @Family = 'Montague'

GO

 

/* Montague RESULTS:

PrisonerName

------------

Romeo

Benvolio

Balthasar

*/

And now it’s time to play the Family Feud! …not really. That just seemed to fit here.

Now that the database code is all set to go, let’s take a look at our report, the Verona Jailing Report. I kept it very simple. It just pulls the list of prisoners for the Family selected.

For the Capulets:

image

For the Montagues:

image

I set up a subscription to the Verona Jailing Report which passes in Capulet for the Family parameter and emails an Excel copy of the report. Here is what the email looks like:

image

Notice that the name of the Excel file (and the report name in the subject line) are just the generic Verona Jailing Report. Until you open the attachment, there is no indication which Family this report is for. You could hard code the family name in the subscription so that it appears in the Subject line, but that still would not meet the requirement of having the Family name part of the Excel file name. Verily, ‘tis true.

The contents of the above Verona Jailing Report look like this:

image

Notice that the worksheet name has also taken on the name of the report. I just used an expression to bring the value of the Family parameters into textbox reading Verona Jailing Report: Capulet.

We therefore create a linked report derived from Verona Jailing Report called Verona Jailing Report – Capulet. We then modify the properties of the Family parameter of the Capulet report in Report Manger so that the user is no longer prompted and set its default value to Capulet. The parameter properties are shown below.

image

We then create a subscription for the Verona Jailing Report – Capulet report just like the one we created for the base report above. Behold:

image

Via our linked report, we have killed two of the birds with one of the stones. Both the subject and the Excel attachment now indicate this report is for the Capulet family. Hazzah!

DISCLAIMER: No birds were harmed during the creation of the post.

The contents of the Verona Jailing Report – Capulet file look like this:

image

We then create a linked report and subscription for the Montague family following the same steps and we have one happy Prince Escalus. And as a bonus, he LOVES the fact that the Montagues and Capulets are unknowingly sharing the report from which their own reports are derived.

And the Montagues and Capulets lived happily ever after….um….wait….

<theater soapbox rant>

While I am here, I need to get this off my chest. One famous line often quoted from Romeo and Juliet is Juliet asking “O Romeo, Romeo, wherefore art thou Romeo?” I just have to make it clear that Juliet is most certainly NOT asking where Romeo is (“Wherefore” means “Why”). Nor is she calling out to a voice-activated tracking system for the homing device she discretely planted on his lapel. Rather, she is calling attention to the lamentable tragedy that she, a Capulet, just fell for Romeo, a Montague, in a world where the Capulets and Montagues are essentially at war. Alas, poor Juliet, if only one of you could be from a different house! It makes more sense when you include more than just the one line:

O Romeo, Romeo, wherefore are thou Romeo?

Deny they father and refuse thy name.

Or if thou wilt not, be but sworn my love

And I’ll no longer be a Capulet.

          ~Romeo and Juliet, William Shakespeare

</theater soapbox rant>

NOTE: I have uploaded the rdl for the my report.

Verona Jailing Report

Jinkies! Passing MultiValue SSRS Parameters To Stored Procedures

SQL Server Reporting Services provides a great way to allows report users to select more than one value from the available values list in parameters, creating a multivalue parameter.

SSRS dynamically creates a comma separated list of the values selected by the user to pass in to the data source.

Great.

It also dynamically creates the Select All option at the top of the available values list.

Also great.

And, it is really easy to enable this feature.

Also also great.

But this great functionality does have a limitation right out of the box. You cannot pass a multivalue parameter to a stored procedure (see Using Single-Value and Multivalue Parameters).

Ruh roh. Not great.

In the past, I have had the data source sproc return all of the records and then used a filter on the data region to only show the records matching the values selected in the parameter. It got the job done, but returning records you are not going to use is suboptimal, to be sure.

Now, you may be thinking, dear reader, that I am a moron for choosing the above title for this post and then almost right away saying that you cannot do it. Well, provided that your database source is SQL 2005 or later, you actually CAN do it through the beauty of XML. And what is really really cool to me is that the amount of work you must perform to achieve this is miniscule. AND, it does not require much XML expertise, which I must confess, I currently lack. I have seen solutions for this obstacle involving the creation of user-defined functions, but I find this XML solution far more elegant and so easy to implement.

As I often do, I must shout out some thanks to Jason Strate (blog: www.jasonstrate.com, twitter: @stratesql) for showing me this.

You may be asking why I am so fond of having reporting data sources be stored procedures instead of just writing the query right into the report. While that is not truly the scope of this post, I will say that I really like having data access code in the database. Also, the security implications are great, since with sprocs, users need only Execute permission on the sproc and none against the underlying tables. There are other reasons, too, but those two are enough for now.

Setting Up The Data

Let’s start off by creating a table to house our data. I am using AdventureWorks (to make it easy) but creating my own schema (to keep things clean). NOTE: I looked but could not find any ScoobyDoo references in AdventureWorks.

USE AdventureWorks

GO

 

CREATE SCHEMA MarkVSQL

GO

 

CREATE TABLE MarkVSQL.ScoobyDooCharacter

(

      CharacterID int not null IDENTITY(1,1)

    , CharacterName varchar(10) not null

)

 

INSERT INTO MarkVSQL.ScoobyDooCharacter VALUES('Fred')

INSERT INTO MarkVSQL.ScoobyDooCharacter VALUES('Daphne')

INSERT INTO MarkVSQL.ScoobyDooCharacter VALUES('Velma')

INSERT INTO MarkVSQL.ScoobyDooCharacter VALUES('Shaggy')

INSERT INTO MarkVSQL.ScoobyDooCharacter VALUES('Scooby Doo')

NOTE: You may be thinking that Scrappy Doo is conspicuously absent from my list. There is a good reason for that. I can’t stand Scrappy Doo. He drives me nuts. I would rather bite down on a metal fork really hard with my front teeth than watch Scrappy Doo. </Scrappy Doo>

Creating Available Values List

Let’s create the stored procedure that will supply our Character parameter with available values. This is where we start to really have some fun.

CREATE PROCEDURE MarkVSQL.GetMysteryIncMemberList

 

AS

 

SELECT

      CharacterName

    , '<Character ID="'

        + CONVERT(varchar,CharacterID)

        + '"/>' AS CharacterIDXML

FROM MarkVSQL.ScoobyDooCharacter

This stored procedure returns the CharacterName along with the CharacterID field that has been modified to return the CharacterID value formatted as an xml node instead of just the int. Here is the fabulous output of our MarkVSQL.GetMysteryIncMemberList sproc.

CharacterName CharacterIDXML

------------- -------------------

Fred          <Character ID="1"/>

Daphne        <Character ID="2"/>

Velma         <Character ID="3"/>

Shaggy        <Character ID="4"/>

Scooby Doo    <Character ID="5"/>

Creating Data Source Sproc 

Now we create the stored procedure that will supply our report with data.

CREATE PROCEDURE MarkVSQL.GetMysteryIncMembers

 

    @CharacterID varchar(max)

    

AS

 

DECLARE @CharacterIDXML xml

 

SET @CharacterIDXML = REPLACE(@CharacterID,',','')

 

SELECT

      c.CharacterName

FROM MarkVSQL.ScoobyDooCharacter c

WHERE c.CharacterID IN

    (SELECT c.value('@ID','int') FROM @CharacterIDXML.nodes('//Character') AS x(c))

There are several things to note about this proc:

  • The parameter is a varchar(max) to allow as much space as we can for the string of comma separated XML nodes that SSRS will pass in.
  • We then declare an XML typed variable, @CharacterIDXML.
  • We remove the commas (which SSRS automatically adds as value separators) from the contents of the @CharacterID parameter and put the resulting list of XML nodes in the @CharacterIDXML variable.
  • Our WHERE clause uses the XML “nodes” method to shred out the CharacterID values from the XML.

Let’s run our MarkVSQL.GetMysteryIncMembers sproc with various parameters as they would be passed in from SSRS.

In this example, we just want to return Fred.

EXEC MarkVSQL.GetMysteryIncMembers '<Character ID="1"/>'

We get the following results:

CharacterName

-------------

Fred

 

(1 row(s) affected)

Now let’s get Fred and Daphne together. I think it is no secret that there is a spark between them.

EXEC MarkVSQL.GetMysteryIncMembers '<Character ID="1"/>,<Character ID="2"/>'

Here they are.

CharacterName

-------------

Fred

Daphne

 

(2 row(s) affected)

Now let’s get all of those meddling kids and their dog.

EXEC MarkVSQL.GetMysteryIncMembers '<Character ID="1"/>,<Character ID="2"/>,<Character ID="3"/>,<Character ID="4"/>,<Character ID="5"/>'

Here they are, in all of their mystery solving glory.

CharacterName

-------------

Fred

Daphne

Velma

Shaggy

Scooby Doo

 

(5 row(s) affected)

The configuration of the parameter in SSRS is nothing out of the ordinary, so I don’t feel it is necessary to go into the step by step process here. You can go here if you need more guidance on the SSRS piece, you can go here: Adding Parameters to Your Report. The article in this hyperlink is for 2008 but includes a hyperlink to the 2005 version as well. The configuration goals are the same, but interface differences between 2005 and 2008 make the exact steps a little different.

You just configure the @CharacterID parameter of the report to do the following:

  • Allow multiple values
  • Get its available values from the MarkVSQL.GetMysteryIncMemberList sproc.
  • Use the CharacterName field as the “Label” field so that the user is presented with the list of names.
  • Use the CharacterIDXML field as the “Value” field so that our XML nodes in a comma separated string are passed to the stored MarkVSQL.GetMysteryIncMembers sproc, just like in our examples above.

That’s really all there is to it. Roh boy!

Find All Deployed SSRS Reports That Reference A Particular Field

This summer I built an SSRS 2008 Report Model for a client. The end users report against the model using Report Builder 2.0.

I shall pause here for a rave.

<rave>

Report Builder 2.0 is just outstanding, in my humble opinion. I built a SQL 2005 Report Model for a different client a few years ago that is still using Report Builder 1.0 and the difference is astounding.

</rave>

We recently needed to change the data type of a field used in the model. The field in question is an integer in the source database but is essentially similar to an account number. The users preferred to have it be a string in their reports so that Report Builder 2.0 would stop insisting on trying to SUM it.

Making the change to the model was no big deal. However, I thought about what would happen to reports that were already created that may be using the field in question and how we could find those reports with minimal effort in case they would need to be updated.

Then I thought to myself, self, the reports are really just files stored in the reportserver database. You should be able to write a query against that db to return the names of any reports which utilize the field in question pretty easily.

After a quick search on the Interwebs, I found this:

http://social.msdn.microsoft.com/Forums/en-US/sqlreportingservices/thread/58bc9be2-cc37-4fde-8a94-de3c7a00c613/

Hazzah! The reports actually reside in the Catalog table in the Content field (Image data type). I ended up creating a script similar to the following with my search string being the name of the field as it appears in the model:

SELECT [name]

FROM reportserver.dbo.catalog

WHERE CONVERT(varchar(max), CONVERT(varbinary(max), content)) LIKE '%fieldname%'

 

This worked like a champ. And it is really easy to come up with other uses for querying the reportserver database. I will be continuing to play with this for sure.