Search This Blog

Thursday, July 28, 2011

Ten Common SQL Server Reporting Services Challenges and Solutions


SSRS offers a range of different reporting techniques and technologies, to cater for the reporting needs of all levels of users, from the chief executives, to business analysts, to operational staff. Their reporting needs range from simple, tabular ad-hoc reports, to parameterized, linked or snapshot reports, to complex drill-down and drill-through multi-level reports.
Following is the list of some of the challenges I have encountered while developing such reports using Reporting Services 2000/2005. In the sections that follow, I will cover each challenge individually, providing insight into what may cause the difficulty, alongside a possible solution.
  1. Horizontal Tables: Calendar Reports
  2. Select "ALL" Query Parameter option
  3. Multiple Sheets in Excel
  4. Excel Merged Cell Issues
  5. Blank Pages
  6. Vertical Text
  7. Report Data in Header/Footer
  8. Are you missing XML/CSV data on your exports?
  9. Template Reports
  10. Using the Reporting Services database
A ZIP file containing samples of the reports detailed in this article is available to download, try out and amend to suit your own needs.

Horizontal Tables: Calendar Reports

The most common need for horizontal display of information, in my experience, is for labeling or for calendar-style reports. There is no native control that allows you to display your data horizontally. There are a few different ways around this, but the easiest way I've found is to use a Matrix control, which allows display of data in a cross-tab or pivot format.
The sample I will be using is of a calendar style report, which will display a report of events which occur in the timeframe displayed. You can build the report from scratch using the steps that I'll outline next, or you can simply import the completed Calendar.rdl file, as part of sample project proved in the code download for this article.
The driving query for this report is shown in Listing 1. The opening lines calculate the required date range for the current month, which may include dates from the prior and forthcoming months, in order to ensure that the results display appropriately on the calendar. The StartDate parameter defines the first Sunday, and the EndDateparameter the last Saturday, to display on the calendar.
The code then creates two Common Table Expressions (CTEs), new to SQL Server 2005 and later. The first,Dates, generates a record for every day in the required date range and the second, Events, simply creates some sample event records for display in the calendar.
Finally, we query these two CTEs, using a ranking function, DENSE_RANK, to assign number to the records based on the date, and various date functions to generate the columns for the matrix control (days of the week), days of the month, event details and so on. The query in Listing 1 is self contained, so all you need to do to test it out is point it to a SQL Server 2005 data source.
DECLARE @StartDate DATETIME, @EndDate DATETIME
--First day of current month
SET @StartDate = DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0,GETDATE()),0))
--First day to display on calendar
SET @StartDate = DATEADD(DAY,-DATEPART(WEEKDAY,@StartDate)+1,@StartDate)
--Last day of month
SET @EndDate = DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0))
--Last day to display on calendar
SET @EndDate = DATEADD(DAY,6-DATEPART(WEEKDAY,@EndDate),@EndDate)

; WITH Dates([Date]) AS (
   --Select First day in range
   SELECT CONVERT(DATETIME,@StartDate) AS [Date]
   UNION ALL
   --Add a record for every day in the range
   SELECT DATEADD(DAY, 1, [Date]) FROM Dates WHERE Date < CONVERT(DATETIME,@EndDate)
), Events AS (
   --Create some events to place on the calendar
   SELECT EventDate = CONVERT(VARCHAR(2),DATEADD(MONTH, -1,GETDATE()),101) + '/30/2009 02:00:00 PM', Note = 'Event 1'
   UNION SELECT EventDate = CONVERT(VARCHAR(2),GETDATE(),101) + '/23/2009 12:00:00 PM',Note = 'Event 2'
  UNION SELECT EventDate = CONVERT(VARCHAR(2),GETDATE(),101) + '/28/2009 02:00:00 PM',Note = 'Event 3'
   UNION SELECT EventDate = CONVERT(VARCHAR(2),GETDATE(),101) + '/28/2009 06:30:00 PM',Note = 'Event 4'
   UNION SELECT EventDate = CONVERT(VARCHAR(2),GETDATE(),101) + '/30/2009 07:00:00 PM',Note = 'Event 5'
   UNION SELECT EventDate = CONVERT(VARCHAR(2),DATEADD(MONTH, 1,GETDATE()),101) +'/01/2009 01:30:00 PM', Note = 'Event 6'
) SELECT
   -- Number the records based on the date, if multiple records have
   -- the same date then they will be numbered the same. Used in
   -- calculation to determine row record is to display on.
   [Order] = DENSE_RANK() OVER (ORDER BY d.[Date]),
   -- date used in all caluclation for date
   d.[Date],
   --generates matrix columns
   [WeekDay] = DATEPART(WEEKDAY, d.[Date]),
   --used to display day of month on calendar
   [Day] = DATEPART(DAY,d.[Date]),
   --used in some calculations for display
   [Month] = DATEPART(MONTH,d.[Date]),
   -- used to get the time of the event
   e.EventDate,
   --event details to display
   e.Note
--CTEs defined above are used as the queries for the results
FROM Dates d
   LEFT JOIN Events e ON CAST(CONVERT(VARCHAR(10),e.EventDate,101) AS DATETIME) =d.[Date]

--Set the maximum times the Dates cte can recurse
OPTION (MAXRECURSION 100)

Listing 1: The Calendar Report Query

Having defined the query, you can build the report. Start with a blank report and add the query as a dsCalendar data set, as shown in Figure 1.
 
Figure 1: Defining the Calendar report data set
Having created the data set, add a Matrix control to the report, as shown in Figure 2.
Figure 2: Adding the matrix control
Figure 2 displays three watermarked areas of the control:
  • Columns - the matrix column header, which we can use to group the column data.
  • Rows the matrix row grouping for row data, which we can use to group the row data.
  • Data – this cell holds the detail data for the report.
I need to use a table control to group and display our detail data, which is each day in the timeframe from theStartDate to the EndDate, so I dragged a table control from the toolbox into the matrix cell watermarked "Data". For this report, I've have made a few changes to the default setup of the table control. For example, I removed one column and the table footer and I've merged the table header cells, as shown in Figure 3.
Figure 3: Adding a table control to the matrix
The next step is to associate the Matrix control with our dsCalendar data set, as shown in Figure 4.
Figure 4: Associating the matrix control with our data set
Next, I need to establish the row and column grouping for our matrix control. To set the row grouping, switch to theGroups tab on the Matrix properties dialog, select the default row grouping item in the list matrix1_rowGroup1 and click the Edit button.
Set the value of the "Group on" Expression to =Ceiling(Fields!Order.Value / 7), as shown in Figure 5. This Ceiling expression is used to determine when a row should break for the next week, which for the most part will be every 7 records.
Figure 5: Grouping the matrix rows
Click OK, and then select the default column grouping item in the list matrix1_ColumnGroup1 and click the Edit button.  This time, for the "Group on" Expression, simply select =Fields!WeekDay.Value for the drop down list and click OK, as shown in Figure 6.
Figure 6: Grouping the matrix columns
The table inside the "Data" region of the Matrix doesn't require any further work. Based on the established matrix row and column groupings, the matrix data will be organized appropriately.
Now that the control of the data is set up, it's time to define the expressions that will determine what data to display in the matrix and table, when the report is rendered.
First, however, I am going to resize the control. We don't need to display anything in the matrix "rows" region, so we minimize the left column of the matrix control, as shown in Figure 7.
Figure 7: Resizing the matrix columns
The next step is to apply the following Expressions to the various report items for display on the report:
  • Matrix Column Header: =WeekdayName(Fields!WeekDay.Value)
    • Displays the days of the week across the top of the report
  • Table Column Header: =Fields!Day.Value
    • Displays the day for each day in the timeframe
  • Table Detail Column 1: =IIf(Fields!Note.Value = Nothing, "", CDate(Fields!EventDate.Value)
    .ToShortTimeString + ":")
    • Displays the time of the event in the first column of the table
  • Table Detail Column 2: =Fields!Note.Value
    • Displays the event details in the second column of the table

Figure 8 shows the Matrix populated with these expressions.
Figure 8: Matrix expressions
When the report is rendered, it will look similar to that shown in Figure 9.
Figure 9: Rendering the Calendar report
Although the report is now functional, it still looks a little unpolished, so the final step is to tweak the layout and formatting until you are happy with it. Figure 10 shows the finalized report, both in layout mode and rendered.

Figure 10: The finalized Calendar report

Select "ALL" Query Parameter option

When using a query to populate an options list for a parameter, sometimes there is a need to select several options at once, rather than an individual option from the provided list. For example, you may want to run a report for multiple companies, instead of each one individually.
In order to do this, you just need to add a UNION clause to the query that is used to populate a drop down of available options for the company parameter. So, for example, if the original query to populate the parameter list might be of the following form:
SELECT CompanyId, CompanyName FROM Company
When rendered, the parameter dropdown list for the report would look as shown in Figure 11.
Figure 11: Selecting individual parameter values
The updated query, allowing users to select all the available parameter values, might look as follows:
SELECT NULL AS CompanyId, 'ALL' AS CompanyName
UNION
SELECT CompanyIdCompanyName FROM Company

Figure 12: Selecting all parameter values
Next, in the data set that uses the value returned from the query parameter, you will need to update your WHEREclause to work appropriately with the updated parameter. For example, if the original WHERE clause looks as follows:
WHERE CompanyId = @Company
The updated WHERE clause will be:
WHERE (@Company IS NULL OR CompanyId = @Company)
Now, as well as being able to filter the data by an individual company, you can cancel the filter by selecting ALL, which sets the @company parameter to NULL and return results from the query as if there was no company filter.

Multiple Sheets in Excel

Have you had a need to create multiple sheets in Excel? To render a report to Excel on multiple sheets, be sure to use page breaks after the different sections of the report. If a section doesn't specifically allow page breaks, then you'll need to wrap the controls inside a rectangle and set the page break property on the rectangle.
Let's say you have a report with two table regions, as shown in Figure 13.
Figure 13: A report with multiple table regions
When you export the report to Excel, you'll find that both the table regions display on the same worksheet, as shown in Figure 14. This makes it hard to make modifications to the Excel file.
 
Figure 14: Two table regions rendered to the same Excel worksheet
To make the table regions display on different worksheets, you can set the PageBreakAtEnd property to True, as shown in Figure 15.
Figure 15: Setting the PageBreakAtEnd property
When the report is exported to Excel, two worksheets will now be created, as shown in Figure 16.
Figure 16: Two table regions exported to two worksheets
In case you are wondering how to rename the sheets when the report is exported to Excel, there isn't a built-in way. You have the option to design a custom rendering extension, buy 3rd party if one supports this, or to modify the excel file post-export.
A more advanced example of this technique is demonstrated in the Report Index.rdl file, as part of the code download.

Excel Merged Cell Issues

Excel can sometimes seem like the worst rendering extension available in reporting services. If you export a report to Excel, and then try to re-sort the exported data, you get a merged cell error. So, unless you completely reformat the export post-export, you cannot resort your columns.
Reporting Services renders everything top down, and there are several ways in which the merged cell problem can occur when you export the report to Excel:
  • If you have anything (controls, images, etc.) laid out above your table/matrix regions
  • If you merge cells in your table/matrix regions
  • If controls from the top of the report do not lineup with controls from your table
One way to help prevent the merged cell issue is to use the technique discussed in the previous section, "Multiple Sheets in Excel". However, multiple sheets are not always the best resolution for this problem, especially when the problem is your page header.
Figure 17 shows an example of a page header containing an image control and a textbox control, which will cause merged cell issues when exported to Excel. Notice that there are gaps between the controls. Each gap, and each control, that does not span the width of the designer will cause a separate column to be created when you export it to Excel.
Figure 17: A page header that will cause merged cell issues
Figure 18 shows the same page header, formatted in a way that will not cause the problem. Notice how the control spans the width of the designer.
Figure 18: A page header that won't cause merged cell issues
What I've done is remove the image control and set a background image for the textbox control. I also added some padding to the textbox control to change the position in which the text will display, so that the image will display left of the text. This will resolve the merged cell issue, caused by having gaps and multiple controls in the page header.
There is also some Device Information Settings that can be used to alleviate some of these merged cell issues. For instance, on export you can set the setting for SimplePageHeaders to True. More details about this setting can be found here:

Blank Pages

Are blank pages a problem for you, when you export/print your reports? In most cases the extra blank pages result from the fact that the body of your report is too wide.
Let's say you want your report printouts to fit on 8.5in x 11in paper, with 0.5in margins on all sides. This means that the maximum width of your report body in the designer can be 7.5in. If it exceeds that value, then you will get the extra pages printed. Most report developers fall into this trap by having their design surface laid out wider than the allowed width of the body of the report, which would be 7.5in, in this example. As you can see in Figure 19, I ensure that my report body is consistent with a portrait layout of a report. My margins are setup as 0.5in on all sides and the report width is set to 8.5in. So when I layout my report I do not want my designer to exceed 7.5in in width to stay within the report margins and report width.
Figure 19: Report Properties and Layout
Another reason blank pages could be created when you export your report is if you allow your controls to grow. If you do, then they can sometimes grow past the maximum page width for your report. You can prevent your controls from growing by setting the following properties of a control from either the properties dialog or properties panel, as seen in Figure 20. Set the "Textbox height" options in the properties dialog, or set the CanGrow properties in the properties panel of Visual Studio.
Figure 20: CanGrow Properties

Vertical Text

Have you ever needed to display your report information vertically, either top-to-bottom or bottom-to-top, rather than left- to-right? There is some support for this in reporting services. For example, you can set the WritingModeproperty of your textbox to tb-rl, as shown in Figure 21.
Figure 21: Setting the WritingMode property of a text box
As a result, the information in the textbox will display top-to-bottom, as shown in Figure 22.
 
Figure 22: Top-to-bottom vertical text
Displaying your text bottom-to-top is a little trickier; you need to create an image and either set the background image of the control to the generated image, or use an image control. Let's take a look at an example. Again, you can either work through the following steps, or download the completed report, VerticalText.rdl.
What is required is a function, shown in listing 2, that will take the text passed in, measure it, and generate an image of appropriate size with the text displayed bottom-to-top.
Shared Function LoadImage(ByVal text As String) As Byte()
  Dim bmp As New System.Drawing.Bitmap(1, 1)
  Dim graphic As System.Drawing.Graphics =System.Drawing.Graphics.FromImage(bmp)
  Dim font As New System.Drawing.Font("Arial", 10)
  Dim width As Integer = graphic.MeasureString(text, font).Width
  Dim height As Integer = graphic.MeasureString(text, font).Height

  bmp = New System.Drawing.Bitmap(height, width)
  graphic = System.Drawing.Graphics.FromImage(bmp)
  graphic.Clear(System.Drawing.Color.White)
  graphic.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias
  graphic.TranslateTransform(0, width)
  graphic.RotateTransform(270)
  graphic.DrawString(text, font, New System.Drawing.SolidBrush(System.Drawing.Color.Black), 0, 0)
  graphic.Flush()

  Dim ms As New IO.MemoryStream
  bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg)

  Return ms.ToArray()
End Function
Listing 2: The LoadImage Function
On the menu bar in Visual Studio, select Report | Report Properties and then paste the above code into the "Code" tab, as shown in Figure 23.
Figure 23: The LoadImage function for displaying vertical text
Next, you will need to add a reference to the System.Drawing namespace, in order to access the basic graphics functionality. Click the "References tab" of the dialog and then the browse ("..") button. Locate the System.Drawingassembly and click "Add". The reference will be added, as shown Figure 24.
 
Figure 24: Adding a reference to Systme.Drawing
Add an image control to the design surface, and then set the MIMEType property to image/jpeg, the Source property to Database, and the Value property to =Code.LoadImage("Hello World"),  as shown in Figure 25. Notice that the value property uses the LoadImage function in our embedded code.
Figure 25: Setting the image control properties
When rendered, the report looks as shown in Figure 26.
 
Figure 26: The rendered report, with top-to-bottom and bottom-to-top vertical text
Natively, SSRS does not allow for text to be displayed at an angle except for in some charts. If you can figure out how to modify the code for the LoadImage function so that it displays the text at an angle and generates an image, you would have a solution for the issue of angled text as well!

Report Data in Header/Footer

Reporting Services does not provide out of-the-box support for use of information from your queries in Page Headers and Footers. There are two ways around this.
The first way is to create controls in the body of your report, holding the values you need to display in the header and/or footer. You can set these controls to "hidden", and place them in some out-of-the-way place, towards the bottom of the report. Then, you can set expressions on controls in your header and footer sections to the value of the control in the body of your report.
In the following example, shown in Figure 27, I placed a textbox control in the body of the report named "textbox1", with a value of "Hello World". In the header section, I placed a textbox with a value of "=ReportItems!textbox1.Value". I then copied the control in the header section and pasted it into the footer section.
Figure 27: Display report data in headers and footers, using a hidden control in the body
If you preview this report, the value of "textbox1" will be displayed in the header and footer, as shown in Figure 28.
Figure 28: Three times Hello World
A slightly cleaner option, in my opinion, is to create a public function that can be called to set the value of a variable, which can then be used in any or all sections of the report body, header, and footer. Figure 29 shows the embedded code that creates this SetReportTitle function, containing the _Title variable.
Figure 29: The SetReportTitle function.
In this example, you can then simply set the value of the hidden textbox in the body of your report to "=Code.SetReportTitle("Report Title")". This calls our function and sets the value of the _Title variable to Report Title.
Now, you can set the value of any control in the header or footer to "=Code._Title". The variable can be used in any or all sections of the report body, header, and footer.

Missing Data in CSV/XML Exports

Is some of your data not getting exported to the data export formats of CSV or XML? Reporting Services, by default, has all data controls set to auto-output on export. This means that the rendering extension whether CSV or XML determines what gets exported. When exporting to the data specific rendering extensions, the extension determines what to export, which in most cases means that tabular data gets exported but not data determined to be informational. There is a way around this feature.
When you click on a control that contains data that you want to export, you should set the propertyDataElementOutput to "Output", as shown in Figure 30.
Figure 30: Setting the DataElementOutput property in preparation for export to CSV or XML.
Alternatively, you can also set this option by right-clicking the control and selecting properties. Once the properties dialog is displayed go to the "Data Output" tab and select the "Yes" option under the "Output" section, as show in Figure 31.
Figure 31: Setting the DataElementOutput property from the Data Output tab
By setting this property to "output", it ensures that your information will get exported. These Data Output options are used only by the CSV and XML rendering extensions. All other built-in formats are exported based on layout and don't use the Data Output settings.

Template Reports

Do you use a predefined layout when you start work on a new report? Do you want to be able to add your report templates to Visual Studio through the "Add New Item" feature? Well, as luck would have it, it's pretty easy to override the built-in template, or add your own templates.
In order to do this, simply navigate to Visual Studio's ReportProject directory, in Windows Explorer:
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\ProjectItems\ReportProject
Note that this path should be accessible if you are using a default installation of Visual Studio. If not, then you'll need to amend the path as appropriate.
This ReportProject folder is the one in which VS stores the Report.rdl file that is used as the default template when you add a new report to your project, as shown in Figure 32.
Figure 32: The default Report.rdl file is stored in the ReportProject folder.
You can replace this default Report.rdl file with your own template, or simply add your own templates to the same folder. In this example, I've added a landscape and a portrait template to the directory as shown in Figure 33.
Figure 33: Adding custom templates
Now, when I choose to add a new item in Visual Studio, my report "templates" are available to be selected, as shown in Figure 34.
Figure 34: The Portrait and Landscape template are available when creating new reports
Note that the "My Templates" area you see in Figure 34 uses a specialized zip structure with some special code to setup how the template is to be used. My report templates are simply "base layouts" for Portrait and Landscape reports that I use to keep things standard. You can obtain the two example templates, PortraitTemplate.rdl andLandscapeTemplate.rdl, form the code download file.

Using the Reporting Services Database

Some report developers don't realize that there are two databases that you can use to lookup or analyze reporting services information. It's often useful to write your own reports, based on information stored in these databases.
The first database is ReportServer, which is used by the Report Services to store all the information about reports that have been uploaded to the report manager. Information such as the report catalog, settings, and security are all stored within the ReportServer database.  The database ReportServerTempDB stores temporary information such as report snapshots, user sessions, and report execution information.
I have three examples of useful reports created from the ReportServer database. The first report is what I call theReport Index. It provides a list of all the items in the reporting services catalog, with links to render each report in the catalog, as shown in Figure 35. This can prove to be useful as it allows your report users to run just one report and get a list of all reports, without having to navigate through the report manager.
Figure 35: The Report Index report
I provide an example Report Index.rdl report as part of the code download with this article. You'll have to point the report to your ReportServer database.
The second report, Report Usage, is basically a metrics-type report, providing details of reports that are being executed and how many times per month. The ReportServer database contains a table called ExecutionLog that, by default, stores every report execution for 60 days. You can update the setting ExecutionLogDaysKept in tableConfigurationInfo to allow for more than 60 days of execution tracking. Again, you can obtain the Report Usage.rdlfile from the download file, and an example of the report is shown in Figure 36.
Figure 36: The Report Usage report
The third report, Report Users, is similar to the Report Usage report. Report Users report is basically a metrics-type report, providing details of which users are executing the reports.  Again, you can obtain the Report Users.rdlfile from the download file, and an example of the report is shown in Figure 37.
Figure 37: The Report Users report
You will need to point all the report mentioned in this section to your ReportServer database.

What has SSRS 2008 fixed?


The challenges that I've covered in this article are ones for which I've managed to finds workable solutions. While using SSRS 2005, I've encountered other challenges for which I still have not found viable solutions, without investigating 3rd party tools. An example would be Rich Text formatting. In SSRS 2005 if you wanted to use Rich Text you have three options none of which are natively supported. You could design your own custom control, generate an image, or buy 3rd party controls.
With SSRS 2208, Microsoft has itself made some 3rd party acquisitions that have made the report developer's life a little easier. For example, Microsoft acquired Dundas Data Visualization and so new data visualization controls, such as Charts and Gauges, are now built-in to reporting services. Microsoft also acquired the OfficeWriter technology from SoftArtisans, Inc., which added Word export and support for Rich Text.
Within Reporting Services, improvements have been made for report authoring, report processing and rendering, programmability, and architecture. Based on the challenges/solutions discussed in this article, the following issues have been specifically addressed in SSRS 2008:
Merged cell issues – The rich text control alleviates some of the merged cell issues when exporting to Excel. Now, you can have one control with multiple formatting options and expressions.
Report data in Header and Footer – Variables have been introduced into Reporting Services that can be global or scoped to groups. You no longer have to hide controls in the body of your report to get data to display in the header and footer sections of your reports. If you don't want to use variables, you can also now use data directly in the header and footer with certain controls.
Report Pagination and rendering– there have been numerous improvements in this area:
  • New properties have been added to allow greater control over how your report is rendered.
  • Null values are now explicit giving you more control while working with nulls.
  • The Tablix control, basically the Table, Matrix, and List controls rolled into one, has drastically improved report rendering capabilities.
  • Visualization improvements for charts and gauges mean they are far superior to the charting capability available out of the box in SSRS 2005
  • The CSV rendering extension has been revamped to work differently depending on the purpose of the export, whether it's for Excel or for application consumption. Overlapping report items should no longer give warnings but may get adjusted automatically when rendered. This reduces pagination problems.
You can get more information about new features in SSRS 2008 from the Microsoft site:
Finally, it's well worth reviewing the list of breaking changes in SSRS 2008, as they may cause some headache and issues in your environment:
You'll probably uncover most of the issues when deploying and configuring the ReportServer. A lot of the configuration options have been removed and/or consolidated. Most significantly, SSRS 2008 no longer relies on IIS, and instead uses Handlers and Routers to work with HTTP.sys directly.

Summary

SSRS is a very easy-to-use reporting architecture but I know from experience that when issues or challenges arise it can be very frustrating. I hope the solutions covered in this article will aid you in your work with SSRS. Remember; when a challenge arises there is always a solution, though the solution may not always feasible, based on available resources.

 for Refrence click here

No comments :

Post a Comment