Creating Multiple-Column Reports [SSRS]
Now and then I get inspired to write about a certain topic as a result of what I encounter on the forums. Here’s one of those moments.
The examples in this article are created using SQL Server 2012 RTM – 11.0.2100.60 (X64). I’m also using the free AdventureWorksDW2012 sample database available at CodePlex. But that doesn’t imply that you actually need 2012 to get this to work. The principle explained in this article should also work on SQL Server 2008 and even 2005.
Have you ever needed to create a report that shows a list of something spread over multiple columns? Then you may have come across a feature called newsletter-style reports. Excited to have found out about that feature, you started creating your report. But alas, upon rendering the preview you discovered that it didn’t work. Further investigation made you conclude that the feature is only supported in two specific renderers (PDF and Image). Ouch!
In this article I’ll be showing you a method to create multiple-column reports that you can actually use with all renderers!
Setting The Scene
We’ve been asked to create a report that shows a list of product codes with each category displayed on a separate page. The codes should be displayed in four columns.
Implementing The Report
To get these requirements implemented, we’ll be using some T-SQL skills in combination with nested tables. So, first step is the dataset query.
The query retrieves only the fields needed in the report plus one additional, calculated field called DisplayColumn. This additional field indicates the column in which the code should get displayed. Here’s what the results look like:
As you can see, the column numbering starts at 1, goes up to 4 and then starts again at 1. It also starts at 1 again at the start of a new category. This is needed because we’ll be grouping the data on category in the report.
Now, let me show you that query and explain a bit how the DisplayColumn is getting calculated:
declare @numberOfColumns int = 4; select dpc.EnglishProductCategoryName, dp.ProductAlternateKey , (ROW_NUMBER() OVER ( PARTITION BY dpc.EnglishProductCategoryName ORDER BY dp.ProductAlternateKey) + @numberOfColumns - 1) % @numberOfColumns + 1 as DisplayColumn from dbo.DimProduct dp inner join dbo.DimProductSubcategory dps on dps.ProductSubcategoryKey = dp.ProductSubcategoryKey inner join dbo.DimProductCategory dpc on dpc.ProductCategoryKey = dps.ProductCategoryKey;
The main ingredient here is the ROW_NUMBER function. According to its definition, it “returns the sequential number of a row within a partition of a result set, starting at 1 for the first row in each partition”.
That brings us to the OVER clause, and its PARTITION BY statement. In our example we partition by category because we need the numbering to start over for each category.
Next up is the ORDER BY part. We order by ProductAlternateKey, which is the product code that our report should display. We want the codes to be ordered alphabetically so the column numbering should take that order into account.
As you can tell, I’m using some numeric trickery on the result of the ROW_NUMBER function. The reason for that is because I’d like my numbers to start at 1 and end at 4. If we would just do “row_number % 4” (% is the modulo operator btw), then our last column would be zero instead of 4.
Okay, that’s part one of the method explained. Over to the visualization part.
Setting The Tables
We’re going to create a table that shows the product category in a header with the product codes spread over four columns below the header.
So, first drag a table into the design canvas and give it an additional column.
To quickly link the table with the dataset, drag the product code field into the detail cell of the first column.
Now we’re going to set up the grouping. We’ll also be making use of nested tables and because tables cannot be nested on the detail level of a tablix, we need to set up grouping even on that detail level. So first, open up the Group Properties of the Details level by right-clicking it in the Row Groups pane:
Click the Add button to add a group and group on the product category:
We want to display a header on the category level, so we’ll need to add another group on top of this one. Right-click the Details row group once more and add a Parent Group:
Group by product category once more and activate the Add group header checkbox:
Here’s what our table now looks like:
We don’t need that first column and we don’t need that top row, so delete both. Now select the four textboxes in the upper row, right-click and select Merge Cells:
This way the header line gets to use all available space. Now put the product category field into the remaining merged cell and render the report:
If all went well you should be seeing the four categories. That means the grouping part is implemented.
Of course, the detail level is not yet as needed because only the first code of each category is being shown and the three other columns are still empty.
So let’s address that now!
Nesting The Tables
Currently our four detail-level cells are just plain textboxes. But textboxes don’t allow filtering and we need filtering because the first column should show only the product codes with DisplayColumn equal to 1, second column 2, and so on.
So what we’re going to do is nest another tablix inside each of those four cells. This nested tablix should be one-celled, so just one column and one detail-level row. That can be done through several ways. I’ll show you two.
To set up the first detail-level cell, drag another Table inside that first textbox (the one that currently contains the product code). That gives us this:
Now select the nested table by clicking one of its cells:
It may be a bit tricky so if needed, temporarily increase the height of the detail-level row to enlarge the nested table.
Now remove two columns and remove the header row of the nested table. Then right-click the grey square in the upper-left corner and open up the Tablix Properties:
These are the properties of the nested table.
Switch to the Filters page, click the Add button and set the filter to DisplayColumn = 1.
Click OK to close the properties.
Now put the product code field again in that first detail-level cell and render the report:
One column done, three remaining!
Let’s set up the second column. This time, drag a List into the second detail-level cell:
A List is in fact a one-celled tablix with a Rectangle inside. But we don’t need that rectangle here, we need a Textbox. So select the rectangle by clicking once inside the cell. You can tell that the rectangle is selected by looking at the Properties window, as shown above.
Now hit the Delete button on the keyboard. In the Properties window, you should see Rectangle changing into Text Box:
Once more we’ve got a one-celled tablix. Set up filtering on DisplayColumn = 2, using the method as explained when setting up the first column (hint: upper-left grey square).
Put the product code field inside the second detail-level cell and render the report:
If all went well, your second column should now display some codes as well.
Two more to go!
Setting up the remaining two columns is actually really easy. Select the nested tablix inside the second cell by clicking the small grey square in the upper-left corner. Then hit CTRL+C, select the third detail-level cell and hit CTRL+V. Also paste into the fourth detail-level cell.
Now change the filtering to 3 and 4 for column three and four and render the report:
All four columns display product codes!
What now remains is some visual cleanup and adding page breaks on the category group. I’m only going into details on the page break.
In the Row Groups of the main table, right-click the upper group and select Group Properties. Then switch to the Page Breaks page and check the Between each instance of a group checkbox:
Render the report to see the final result:
The first page displays the product codes of the first category, spread over four columns! The other pages contain the other categories.
Even though it takes a bit of work and some tricks, it is possible to create multi-column (aka newsletter-style) reports using basic SSRS components whilst still supporting all renderers.
I hope you enjoyed this article, have fun!
- Silly SQL #1: The Movie! [SSIS]
- Silly SQL #1: OLE DB Destination [SSIS]
- Deploying PDFs, and more [SSRS]
- T-SQL Tuesday 50: Automation, Automation, Automation!
- The "Select ALL" parameter option [SSRS]
- Book: SQL Server 2012 Reporting Services Blueprints
- SQL Server Days 2013 – Data Cleansing: Download
- SQL Server Days 2013
- Exploring the System.Object Package Variable [SSIS]
- T-SQL Tuesday 46: Contraptions!
- December 2014 (1)
- August 2014 (1)
- May 2014 (1)
- January 2014 (1)
- December 2013 (1)
- November 2013 (2)
- October 2013 (1)
- September 2013 (2)
- July 2013 (2)
- June 2013 (2)
- May 2013 (3)
- March 2013 (3)
- February 2013 (2)
- January 2013 (2)
- December 2012 (2)
- November 2012 (3)
- October 2012 (2)
- August 2012 (2)
- July 2012 (2)
- June 2012 (2)
- May 2012 (2)
- April 2012 (3)
- March 2012 (4)
- February 2012 (4)
- January 2012 (2)
- December 2011 (2)
- November 2011 (2)
- October 2011 (1)
- September 2011 (3)
- August 2011 (2)
- June 2011 (2)
- May 2011 (3)
- April 2011 (3)
- March 2011 (3)
- February 2011 (2)
- January 2011 (5)
- December 2010 (1)
- November 2010 (3)
- October 2010 (3)
- September 2010 (2)
- August 2010 (4)
- July 2010 (2)
- June 2010 (4)
- May 2010 (6)
- April 2010 (3)
- March 2010 (3)
- February 2010 (11)
- January 2010 (9)
- December 2009 (2)
- November 2009 (3)
- October 2009 (3)
- September 2009 (4)
- August 2009 (6)
- July 2009 (2)
- June 2009 (3)
- May 2009 (7)
- April 2009 (3)
- March 2009 (3)
- February 2009 (5)
- January 2009 (4)
- December 2008 (2)
- November 2008 (3)
- October 2008 (1)
- September 2008 (1)
- August 2008 (4)
- July 2008 (3)