7/22/2011

The EXCEPT and INTERSECT Operators in SQL Server

Ref:

The UNION,  EXCEPT and INTERSECT operators of SQL enable you to combine more than one SELECT statement  to form a single result set. The UNION operator returns all rows. The INTERSECT operator returns all rows that are in both result sets. The EXCEPT operator returns the rows that are only in the first result set but not in the second. Simple? Rob Sheldon explains all, with plenty of examples.
Quite often, you’re faced with the task of comparing two or more tables, or query results, to determine which information is the same and which isn't. One of the most common approaches to doing such a comparison is to use the UNION or UNION ALL operator to combine the relevant columns of the results that you want to compare. As long as you adhere to the restrictions placed on either of those operators, you can combine data sets whether they come from different databases or even different servers. With the UNION operator, you end up with a result containing every distinct row from the two results combined. However, it becomes more difficult to use UNION to return only the common data that is held in both results, or the different data that exists in one table but not the other(s). To get the results you need, you must use UNION ALL with a GROUP BY clause, though the logic isn’t always obvious on how to do this. And it isn’t any easier to use a JOIN operator to get the result you want. .
Enter the INTERSECT and EXCEPT operators. Beginning with SQL Server 2005, you can use these operators to combine queries and get the results you need. For instance, you can use the INTERSECT operator to return only values that match within both data sets, as shown in the following illustration .
The illustration shows how the INTERSECT operator returns data that is common to both results set; the common data is represented by the area where the two circles intersect. The illustration also shows how the EXCEPT operator works; only data that exists in one of the data sets outside the intersecting area is returned. For instance, if Set A is specified to the left of the EXCEPT operator, only values that are not in Set B are returned. In the illustration above, that would be the data in the left circle, outside the section where the two data sets intersect. The following bullets sum up which operator to use to return different combinations of data:
  • To return the data in Set A that doesn’t overlap with B, use A EXCEPT B.
  • To return only the data that overlaps in the two sets, use A INTERSECT B.
  • To return the data in Set B that doesn’t overlap with A, use B EXCEPT A.
  • To return the data in all three areas without duplicates, use A UNION B.
  • To return the data in all three areas, including duplicates, use A UNION ALL B.
  • To return the data in the non-overlapping areas of both sets, use (A UNION B) except (A INTERSECT B), or perhaps (A EXCEPT B) UNION (B EXCEPT A)
The differences between the INTERSECT and EXCEPT operators and how to use each of them will become clearer as we work through the examples in the article. Just to give you a basic idea of how they work, we’ll start with a rather unrealistic example. To demonstrate those, however, we must first create two test views (using SQL Server 2005–compatible syntax). The first view contains a single column that describes what you might have had for lunch:
CREATE VIEW Lunch
AS
  SELECT 'Beer' AS item
  UNION SELECT 'Olives'
  UNION SELECT 'Bread'
  UNION SELECT 'Salami'
  UNION SELECT 'Calamari'
  UNION SELECT 'Coffee';
GO
The second view also contains a single column and describes what you might have had for dinner:
CREATE VIEW Dinner
AS
  SELECT 'Wine' AS item
  UNION SELECT 'Olives'
  UNION SELECT 'Bread'
  UNION SELECT 'Steak'
  UNION SELECT 'Aubergines'
  UNION SELECT 'Salad'
  UNION SELECT 'Coffee'
  UNION SELECT 'Apple';
GO
Now we can use these two views to demonstrate how to use the UNION, INTERSECT, and EXCEPT operators. I’ve also included a couple examples that use the JOIN operator to demonstrate the differences.
The first example uses the UNION operator to join the two views in order to return everything you’ve eaten today:
SELECT item FROM Lunch
UNION
SELECT item FROM Dinner;
Now we return the same data by using a full outer join:
SELECT DISTINCT COALESCE(Lunch.item, Dinner.item) AS item
FROM Lunch
  FULL OUTER JOIN Dinner
  ON Dinner.item = Lunch.item
Notice that the join requires more complex syntax; however, both statements return the same results, as shown in the following table:
item
Apple
Aubergines
Beer
Bread
Calamari
Coffee
Olives
Salad
Salami
Steak
Wine

Now let’s look at how you would return only the food you ate (or drank) for lunch, but did not have for dinner:
SELECT item FROM Lunch
EXCEPT
SELECT item FROM Dinner;
In this case, I used the EXCEPT operator to return the lunch-only items. I could have achieved the same results using the following left outer join:
SELECT Lunch.item
FROM Lunch
  LEFT OUTER JOIN Dinner
  ON Dinner.item = Lunch.item
WHERE dinner.item IS NULL;
Once again, you can see that the join is more complex, though the results are the same, as shown in the following table:
Item
Beer
Calamari
Salami

If you wanted to return those items you had for dinner but not lunch, you can again use the EXCEPT operator, but you must reverse the order of the queries, as shown in the following example:
SELECT item FROM Dinner
EXCEPT
SELECT item FROM Lunch;
Notice that I first retrieve the data from the Dinner view. To use the left outer join, you would again have to reverse the order of the tables:
SELECT dinner.item
FROM dinner
  LEFT OUTER JOIN Lunch
  ON Dinner.item = Lunch.item
WHERE Lunch.item IS NULL;
As expected, the results are the same for both SELECT statements:
item
Apple
Aubergines
Salad
Steak
Wine

In the next example, I use the INTERSECT operator to return only the food that was eaten at both meals:
SELECT item FROM Dinner
INTERSECT
SELECT item FROM Lunch;
As you can see, I simply connect the two queries with the INTERSECT operator, as I did with the EXCEPT operator. You can achieve the same results by using an inner join:
SELECT Dinner.item
FROM Dinner
  INNER JOIN Lunch
  ON Dinner.item = Lunch.item;
As the following results show, the only items you had at both meals were olives, bread, and coffee:
item
Bread
Coffee
Olives

Now let’s look at how you would return a list of food that you ate at one of the meals, but not both meals, in other words, the food you ate other than bread, olives, and coffee. In the following statement, I use a UNION operator to join two SELECT statements:
SELECT item
FROM
  (
    SELECT item FROM Lunch
    EXCEPT SELECT item FROM Dinner
  ) Only_Lunch
UNION
SELECT item
FROM
  (
    SELECT item FROM Dinner
    EXCEPT SELECT item FROM Lunch
  ) Only_Dinner; --Items you only ate once in the day.
Notice that first statement retrieves only the food you ate for lunch, and the second statement retrieves only the food ate for dinner. I achieve this in the same way I did in previous examples—by using the EXCEPT operator. I then used the UNION operator to join the two result sets. You can achieve the same results by using a full outer join:
SELECT COALESCE(Dinner.item, Lunch.item) AS item
FROM Dinner 
  FULL OUTER JOIN Lunch
  ON Dinner.item = Lunch.item
WHERE Dinner.item IS NULL OR Lunch.item IS NULL;
In both examples, the statements return the following results:
item
Apple
Aubergines
Beer
Calamari
Salad
Salami
Steak
Wine
From this point on, I developed the examples on a local instance of SQL Server 2008 and the AdventureWorks2008 database. Each example uses either the INTERSECT or EXCEPT operator to compare data between the Employee and JobCandidate tables, both part of the HumanResources schema. The comparison is based on the BusinessEntityID column in each table. The BusinessEntityID column in the Employee table is the primary key. In the JobCandidate table, the BusinessEntityID column is a foreign key that references the BusinessEntityID column in the Employee table. The column in the JobCandidate table also permits null values.
NOTE:
You can run these examples against the AdventureWorks database on an instance of SQL Server 2005. However, you must first change the BusinessEntityID column name to EmployeeID, and you must change the JobTitle column name to Title.

Working with the INTERSECT Operator

As I mentioned above, when you use the INTERSECT operator to combine queries, the data returned is only that which matches between the two data sets. That means, with regard to the Employee and JobCandidate tables, the result set will include only rows in which the BusinessEntityID value in the Employee table matches the BusinessEntityID value in the JobCandidate table.
In the following example, I create two queries that retrieve data from the Employee and JobCandidate tables and use the INTERSECT operator to combine those queries:
SELECT BusinessEntityID
FROM HumanResources.Employee
INTERSECT
SELECT BusinessEntityID
FROM HumanResources.JobCandidate;
The first SELECT statement, as you can see, retrieves the BusinessEntityID column from the Employee table, and the second SELECT statement retrieves the BusinessEntityID column from the JobCandidate table. The INTERSECT operator combines the two queries.
When you use the INTERSECT operator to combine queries (or EXCEPT, for that matter), the number of columns must be the same in both queries and the columns must be in the same order. In addition, the corresponding columns between the queries must be configured with compatible data types. The example above meets these conditions because each query returns only one column of the same data type (INT). When the INTERSECT operator is used to combine these the two queries, the statement returns the following results:
BusinessEntityID
212
274
As it turns out, the Employee table and JobCandidate table have only two BusinessEntityID values in common. If you were to examine the data in the JobCandidate table, you would find that the query results here are consistent with the data in that table. The table contains only 13 rows, and the BusinessEntityID column is NULL for all but two of the rows. The BusinessEntityID values in those rows are 212 and 274. And, as you would expect, the Employee table also contains a row with a BusinessEntityID value of 212 and a row with a value of 274.
Certainly, as the above example indicates, using the INTERSECT operator to combine the results of two queries together is a straightforward process. The key, as I’ve stated, is to make sure the SELECT lists in the two queries are in sync with each other.
However, that also points out one of the limitations of using the INTERSECT operator to combine queries—and that is, you cannot include columns in one query that are not included of the second query. If you do include multiple matching columns in each query, all the column values must match in order for a row to be returned. For example, suppose you’re retrieving data from two tables that each include columns for employee IDs, first names, and last names. If you want to match the two tables based on those three columns, the three values in the first table must match the three values in the second table for a row to be returned. (At this point, you might be asking yourself what you’re doing with all that redundant data in your database.)
Instead of taking this approach, you may decide to compare the IDs in both tables, but pull the first and last name from only one of the tables. Or you might decide that you want to pull information from one table that is not stored in the other table. However, because columns must correspond between the two queries when using an INTERSECT operator to combine them, you have to find a way to work around this limitation. One of the easiest ways to do that is to put your INTERSECT construction into a common table expression (CTE) and then join the expression to one of the tables to pull the additional data. For instance, the following example includes a CTE that contains the same INTERSECT construction you saw in the example above:
WITH
  cteCandidates (BusinessEntityID)
  AS
  (
    SELECT BusinessEntityID
    FROM HumanResources.Employee
    INTERSECT
    SELECT BusinessEntityID
    FROM HumanResources.JobCandidate
  )
SELECT
  c.BusinessEntityID,
  e.LoginID,
  e.JobTitle
FROM
  HumanResources.Employee AS e
  INNER JOIN cteCandidates AS c
    ON e.BusinessEntityID = c.BusinessEntityID
ORDER BY
  c.BusinessEntityID;
Notice that I’ve created a CTE named cteCandidates. As you would expect, the CTE returns the BusinessEntityID values that are contained in both the Employee and JobCandidate tables. In the primary SELECT statement, I then join the Employee table to the CTE in order to also retrieve the LoginID and JobTitle values from the Employee table. Because I put the INTERSECT join in the CTE, the statement can now return the following results:
BusinessEntityID LoginID JobTitle
212 adventure-works\peng0 Quality Assurance Supervisor
274 adventure-works\stephen0 North American Sales Manager
As you can see, I’ve gotten around the limitations of the INTERSECT operator and am now returning additional information from one of the tables. I could have also joined the CTE to a different table in order to include additional information. For example, I might have joined what I have here to the Person table to retrieve the employee’s first and last names. The point is, the CTE let’s you be quite flexible when working with the INTERSECT operator; you can still determine which rows match but also return all the data you need, regardless of the source table.

Working with the EXCEPT Operator

Once you’ve mastered the INTERSECT operator, you have, for the most part, mastered the EXCEPT operator. As I stated earlier, the EXCEPT operator returns only those rows that do not have a match in the joined table. The important point to remember when using the EXCEPT operator is that it makes a difference which side of the operator you place the query. Only data from the query to the left of the operator (before the operator) is returned. Let’s look at an example to demonstrate how this works.
In the following statement, I again combine two queries, one that retrieves data from the Employee table and one that retrieves data from the JobCandidate table:
SELECT BusinessEntityID FROM HumanResources.Employee
EXCEPT
SELECT BusinessEntityID
FROM HumanResources.JobCandidate;
This statement is nearly identical to the INTERSECT construction you saw in the first two examples, except, of course, for the use of the EXCEPT operator. However, because the query to the left of the operator is retrieving data from the Employee table, the final result set will include data only from that table, and not the JobCandidate table.
The Employee table, as it turns out, contains 290 rows. As you’ll recall from the previous examples, the two rows in the table with the BusinessEntityID values of 212 and 274 match the two rows in the JobCandidate table that also have BusinessEntityID values of 212 and 274. That means, these two rows should be excluded from the result set of the query above, which is exactly what happens. The query returns 288 rows that have BusinessEntityID values of 1 through 290. However, IDs 212 and 274 are not included in those results.
Now let’s look at what happens when you reverse the order of the queries, as I’ve done in the following example:
SELECT BusinessEntityID FROM HumanResources.JobCandidate
EXCEPT
SELECT BusinessEntityID
FROM HumanResources.Employee;
Notice that the query that retrieves data from the JobCandidate table now comes first, that is, sits to the left of the EXCEPT operator. The results from this statement, as you would expect, are quite different from the previous example. All that is returned is a single NULL value. In other words, according to the results, the JobCandidate table contains no BusinessEntityID values that are not contained in the Employee table. This is, of course, exactly the case.
As with the CTE example above, which uses the INTERSECT operator, you can also use CTEs with EXCEPT operators. But as the last example points out, if your CTE returns no data, your main query will also return no data (at least if you’re using an inner join), but that’s true with either operator. Overall, in fact, you’ll find there’s little difference between the INTERSECT and EXCEPT operators, in terms of how you use them. The difference, of course, is in the results. INTERSECT returns rows common to both queries, and EXCEPT returns only rows in the left query. Both operators, however, are useful additions to the types of joins that the UNION and UNION ALL operators provide. You can find more details about the INTERSECT and EXCEPT operators by referring to the topic “EXCEPT and INTERSECT (Transact-SQL)” in SQL Server Books Online. There you’ll find additional information about each operator and additional examples of how to use them.

Get Started with Chart Development in Reporting Services 2008

Ref:

With Reporting Services 2008, it is now much easier to produce charts from data in SQL Server, and the variety of charts seems almost limitless. All you need to get started  is a quick step-by-step guide  that tells you the basics and gets you past the stage of creating the first chart. Well, here it is.
One of the greatest rewards of developing any type of report is to transform the vast amounts of business data into useful  information that can support commercial decision-making; producing such  reports as the  performance of an internal process, percentage of an employees’ contribution to overall product sales, or a department’s budget compared to other departments.  It can become a real challenge for the developer of a report to present this vast amount of information properly, because the correct interpretation of data by the user is as important as the data itself.  
One solution is to present the data in an aggregated format so that business users can more easily and quickly digest this information. The chart is a tool designed specifically for the presentation of aggregated data. If it is done properly, it is possible for the  user to quickly grasp the information available as they dash off to their next meeting, without needing to scroll through, and assimilate,  a huge list of tabular results.

Reporting Services 2008 Charts:

Reporting Services 2008 provides an extensive array of charting capabilities which have been greatly improved, and are much more intuitive than before. Reporting Services 2008, now, includes 8 different chart types:
  • Column
  • Line
  • Shape
  • Bar
  • Area
  • Range
  • Scatter
  • Polar

Reporting Services Chart Data Regions:

Because charts contain data, they are considered, in Reporting Services parlance, to be a data region. A chart data region operates just like any other data region and uses groups, query parameters, and filters much the same way as a table, list, or matrix. Charts operate just like any other data region. Nonetheless, charts  contain some unique characteristics that separate them from the other data regions.
In this article, you will be creating a basic column chart using data from the AdventureWorks data warehouse database, AdventureWorksDW2008R2. The chart will present AdventureWorks total sales grouped by year and region.

Chart Basics

The chart data of Reporting Services is organized into three main areas: values, category groups and  series groups.
  • Category groups are distributed along the x-axis or horizontal axis.
  • Values are shown along the y-axis or vertical axis.
  • Y-axis values must be a numeric data type.
  • A chart, like any other data region, is attached to one particular dataset.
  • You can group data within each axis.
  • At minimum, a chart should have one aggregated field for the value and one grouped field for the category.
  • Charts cannot be created using the Report Project Wizard. The Report Project Wizard does not support chart or free-form data regions.

Creating the Basic Chart Report:

To create a project, Open BIDS and choose ‘File, New, Project’ which opens the New Project dialog box.
When Reporting Services is installed, a new project type is added that is called ‘Business Intelligence Projects’ (located in the ‘Project types’ pane in the upper-left hand corner). This type includes templates for SQL Server Integration Services (SSIS), SQL Server Analysis Services (SSAS) and SQL Server Reporting Services (SSRS). The Reporting Services templates are as follows:
  • Report Server Project – produces a blank project.
  • Report Server Project Wizard – very similar to Report Server Project template except it automatically starts the Report Wizard.
  • Report Model Project – creates a metadata description of a database which is used for ad-hoc reporting.
Choose the Report Server Project template, enter a project name, “AdventureWorksReporting” and Click OK. 
BIDS Report Designer opens, displaying the Start Page in the center of the interface. Notice, also, in Solution Explorer, that two folders are created called ‘Shared Data Sources and Reports’.
Your first step in creating any reporting project should be to create a data source. A Reporting Services data source stores information about the connection that is used to access databases or other resources that return data. Reporting Services supports two types of data sources: shared and embedded (also called private). A shared data source is stored independently from the report and can be used in many reports.  An embedded or private data source can be used only within one report so its use is limited to one report. For this reason, it is best to use shared data sources whenever possible.
To create a shared data source, in the Solution Explorer pane, right-click the ‘Shared Data Sources ‘folder and select ‘Add New Data Source’ from the context menu.
The ‘Shared Data Source Properties’ dialog box displays.
Enter a descriptive name for the new datasource,  the correct provider, and then click the ‘Edit…’ button to create the data source connection string. The ‘Connection Properties’ dialog box appears which is used to create a connection string. Enter your proper credentials.

Add a Report to the project:

In Solution Explorer, right-click the Reports folder and select ‘Add, New Item’ (note: Do not choose the first item from the context menu, “Add New Report”. This option will start the Report Wizard which does not support the chart data region).
This will display the ‘Add New Item’ dialog box.
Under ‘Templates’, Select ‘Report’ and enter a descriptive report name. Click ‘Add’.
This opens the report in the Report Designer.
Note: Report Layout
Before creating any reports, you need to be aware of how SSRS report layout works. This will help you to build reports that will render and paginate more consistently  across the different SSRS rendering extensions. Rendering is the process of generating a document or image from data. Reporting Services 2008 supports seven different rendering extensions:
CSV, Excel, HTML, Image, PDF, Word and XML

The rendering Extension that you use will affect the way that pagination works. Pagination, as it relates to Reporting Services, is the process of dividing information or content into discrete pages. For example, Image, PDF, and Word formats let you precisely control where the pages break, while Excel and HTML do not. To control pagination, you need to know how a report fits onto a page. When you create a report, two design elements are automatically created; Report and Body. Report, the outer most element,  has a property called ‘Page Size’. By default, ‘Page Size’ is set to 8.5  by 11 inches with each margins set to 1in. The Body element is located inside the Report element;  all report content fits onto. The Body element. It has a property called Size which, by default, is set to 6.5 by 2 inches. You can set the Body Size properties interactively by resizing the design surface in the Report Designer using your mouse or by setting the Width and Height property values in the Body’s Properties pane.  If you do expand the Body element’s Width and Height larger than the Report elements Page Size, the Page Size property remains the same. This could spoil the rendering of  the report.

 
Apply the following formula to calculate the report page width:
      Report Width = Body Width + Report Left Margin + Report Right Margin

You need to verify that the result does not exceed the Report’s Page Size.Width value, otherwise, you will encounter problems when viewing reports in different formats.

To add a chart to a report:

From the Toolbox pane on the left-side of the Designer, drag a chart report item onto the Report Designer. Once you drop the chart onto the design surface, the “Select Chart Type” dialog box appears.
Choose the first chart from the Column chart type. Click OK.
Next, the Data Source Properties dialog box appears. Enter a descriptive name for the data source, choose the “Use shared data source reference” radio button and select the shared data source created previously from the drop-down menu. Click ‘Next’.

This will bring you to the Query Designer dialog box. Keep Text selected in the ‘Command type’ drop-down menu and enter the following query into the Query text box:
SELECT  d.CalendarYear AS Year,
            s.SalesTerritoryCountry AS Region,
        CAST(SUM(f.SalesAmount) AS DECIMAL(18,2)) AS SalesAmount
FROM    dbo.FactInternetSales AS f
        INNER JOIN dbo.DimDate AS d ON f.OrderDateKey = d.DateKey
        INNER JOIN dbo.DimSalesTerritory as s
        ON
        f.SalesTerritoryKey = s.SalesTerritoryAlternateKey
GROUP BY d.CalendarYear,s.SalesTerritoryCountry
To verify the results of the query, click the exclamation point button on the toolbar.
Your results should look similar.
Click Finish, which will take you to the Report’s ‘Report Designer’ interface.
Notice the item directly below the ‘data source ‘ labeled ‘AdventureWorksDataSource’in the Report Data Pane on the left-side of the Designer. This item is called a dataset. A dataset is the means by which reports access data. In Reporting Services, reports never directly reference data sources; reports use datasets instead. Datasets do not contain the actual data, just the query syntax needed to retrieve the data. The fields or the columns in the dataset are listed below the datasets. These fields are what you will use to add data to the chart.
By default, the size of a  chart is 3in wide and height of 2in. To expand the chart’s size, click the chart in the Report Designer and put your cursor over the circle in the upper-left hand corner of the highlighted chart. Once the cursor displays two arrows along both ends, hold down the left click and expand the chart to the desired size (you can also change the width and height of the chart in the Charts Properties pane).
A chart isn’t much use without some data. To add data fields to the chart, click the middle of the chart in the Report Designer.  
The chart data drop down fields will be displayed along the top, bottom, and right side of the highlighted chart. These are the areas into which you will add the dataset fields. With the Report Data pane still open, drag the Year field from the dataset to the Drop category fields here (bottom) drop zone and drag the ‘SalesAmount’ field to the ‘Drop data fields here’ (top) drop zone. Now preview the results by clicking the ‘Preview’ tab in the Report Designer.
The charts is not going to win any design contests but even at this stage the chart contains usable information.
Now, go back to the Design tab and drag the Region Field to the ‘Drop series fields here’ drop zone. This will further subdivide the ‘SalesAmount by Region’ and preview the chart again.
To enhance the chart’s look and feel, you will at least  need to
  • remove the x-axis title
  • update the y- axis and chart titles
  • format the y-axis values as currency
  • move the legend beneath the chart to allow for more horizontal space for the columns
To remove the x-axis title, go back to the Design tab. Click in the area directly below the chart on the label titled, “Axis Title” and press Delete. Next, to update the y-axis title, click on the vertically-aligned y-axis label titled, “Axis Title”. Once selected, change the label from “Axis Title” to “Sales Amount”. To update the Chart Title, click on the phrase, “Chart Title”, and enter “Sales Amount by Year/Region”.
To format the y-axis data value as currency, right-click on the y-axis and select ‘Axis Properties…’
 
This displays the ‘Value Axis Properties’ dialog box.
From the ‘Value Axis Properties’ dialog box, select the ‘Number’ tab, select ‘Currency’  from the ‘Category’ List, set the Decimal places as 0 and select the ‘Use 1000 separator(,)’ check box. Then  Click OK.
Finally, in order to move the legend below the chart, right-click the legend and select Legend Properties. From the General tab, change the Layout drop down menu from Auto table to Row and change the legend position button to the lower-right corner button. Click OK.
Preview the Report.
Now build and deploy the chart to Report Manager.

Business Uses:

Once the report is deployed you can export the report to Excel. The chart is rendered as a.png file format inside the Excel Worksheet allowing you to expand the size of the chart without losing much, if any, image quality.
Additionally, business users can copy the image directly from Excel and paste it into a PowerPoint slide, or any other publishing application.

Wrap Up:

I hope I have shown you how easy it is to develop charts in Reporting Services 2008. This article gives step-by-step instructions on the basics of chart development so as to get you up and running. Much more could be written about different aspects of this powerful data region, but once you have broken the barrier of the first report it is much easier to  explore, for yourself ,  the almost endless charting possibilities that are available in Reporting Services 2008.

Reporting Services with Style

Ref:

Even when Microsoft neglect a feature, there are always pioneers who find ways of getting around the problem. Such is true of Reporting Services, where there are surprising restrictions in the way that reports can be made to conform to a standard style. Barry King comes up with a surprisingly simple solution for SQL Server 2005 and 2008.
One thing has always bothered me about Reporting Services; why didn’t Microsoft include some kind of style-sheet ability within its report rendering engine? When a report is rendered as an HTML page, surely it would make sense to allow Cascading Style-Sheets (CSS) to be used, or at least enable the style to be configured by the user? Well, they didn’t, and perhaps if they are reading they will include something like this in future releases.
So, is it possible at all? Well, as luck would have it there are a few different ways you can do this.
Microsoft provided some opportunities when they allowed for properties to be configured with expressions. This means you can change the style; font, border etc. of any element on a report, based on some logic.
What I will show you here are two possible ways you can make use of this, the latter being a lot more dynamic and configurable.
The first solution has been documented in more detail by Adam Aspin on SQL Server Central, the full article can be found at http://www.sqlservercentral.com/articles/SSRS/65810/.  I feel this has some drawbacks, although this may be enough for your requirements. This will rely on the use of the cumbersome but useful embedded code functionality of reporting services. 
 Anyone who has used this on a regular basis will confirm that this is not a great experience. Code completion? highlighting? in fact any kind of programming IDE features? No, not here.  It reminds me of writing MS Access 1.0/2.0 code where you had an idea it might work but didn’t really know until you ran it and its quite surprising to find such a poor coding experience in a mature product like Reporting Services
You will need to add a new function to your report that will act as your style guide for the report. You do this by opening an existing report or creating a new one:  Then you need to select  Report->Report Properties from the menu (this relates to Visual Studio 2005, other versions may vary slightly) and then click the Code tab. Below is an example function you can use.
Function StyleElement (ByVal Element As String) As String
        Select Case UCase(Element)
            Case "TABLE_HEADER_BG"
                Return "Red"
            Case "TABLE_FOOTER_BG"
                Return "Green"
            Case "TABLE_HEADER_TEXT"
                Return "White"t;
            Case e Elsese
                Return "Black”k”
        End Select
End Function
Now apply this function to the style property of an element on the report. You do this by bringing up the properties for the element, and then typing the following code into the value for the property (instead of selecting value from a drop down list).
=code.StyleElement("TABLE_HEADER_TEXT")           
This will replace the current value with the value returned by the function for the style you requested. Although this is simple, this way of styling has a couple of major drawbacks.
You can share the code fairly easily between reports in as much as you can copy and paste the report code which makes this process straightforward. For me, the biggest problem is this: If you invest the time and effort in changing all your reports to use this method of dynamically styling your report and you then subsequently decide to change the style,  you will have to go through each report and change the code. The code is obviously hard-coded so is it really dynamic? It would seem to me to be much better to  change some kind of external configuration.
The solution I will present here allows you to configure your styling externally, and change this on the fly. You can then use predefined styles that are based on a parameter that you can pass to the report. Sounds cool, huh? Well, the magic starts with a couple of tables to hold the styling information.

CREATE TABLE ReportStyle(
    
ReportStyleId INT IDENTITY (1, 1),
    
StyleName VARCHAR (80)
)
CREATE TABLE Element(
    
ElementId INT IDENTITY (1, 1),
    
ElementName VARCHAR (80),
    
ElementDefaultValue VARCHAR (80)
)
CREATE TABLE ReportStyleElements(
    
ReportStyleId INT
    
ElementId INT,
    
ElementValue VARCHAR (80)
)
The ReportStyle table is way for you to define multiple styles (templates) that you can switch, if you want, on the fly. In our organisation we have multiple brands that we report on; each brand can have its own styling and use a single report to do so.
The Element table holds a row for each 'stylable' element on your reports; a default value will be stored against the element so that you don’t have to specify all the different elements when you define a report style. Try to come up with a definitive list of Elements, even if you will not use them. There is a limit of 1024 elements , this being the maximum number of columns in SQL Server 2005. This restriction is because of the way we query this later. I really don’t think you will ever reach that limit; you are more likely to have around 30 in total depending on how complex your styling is.
Once defined and used on a report, try not to change the name of the element as this will break existing reports. Think of defining the names as part of your company’s standards, so that the names should change infrequently. You can add additional elements with no impact, apart from the 1024 element  limit I've mentioned.
To define a report style, you insert values into the ReportStyleElements table, specifying any elements  for which you want alternative styling (different from the default value). 
For the purpose of this article, I am showing just 4 basic elements and how you can use these on your reports.

-- Insert Elements

SET IDENTITY_INSERT [Element] ON
INSERT INTO [Element]([ElementId],[ElementName],[ElementDefaultValue] )VALUES  (1,'TABLE_HEADER_BG', 'WHITESMOKE')
INSERT INTO [Element] ([ElementId],[ElementName],[ElementDefaultValue] )VALUES  (2,'TABLE_HEADER_TEXT','BLACK')
INSERT INTO [Element] ([ElementId],[ElementName],[ElementDefaultValue])VALUES  (3,'TABLE_DETAIL_BG','WHITE')
INSERT INTO [Element] ([ElementId],[ElementName],[ElementDefaultValue])VALUES  (4,'TABLE_DETAIL_TEXT','BLACK')
SET IDENTITY_INSERT [Element] OFF-- Report StyleSET IDENTITY_INSERT [ReportStyle] ON
INSERT INTO [ReportStyle] (ReportStyleId,StyleName)VALUES (1, 'Stlye 1')INSERT INTO [ReportStyle] (ReportStyleId,StyleName)VALUES (2, 'Stlye 2')
SET IDENTITY_INSERT [ReportStyle] OFF-- Report Style Elements INSERT INTO [ReportStyleElements] ([ReportStyleId], [ElementId], [ElementValue] )VALUES ( 1, 1, 'BLUE' )INSERT INTO [ReportStyleElements] ([ReportStyleId], [ElementId], [ElementValue] )VALUES ( 1, 2, 'WHITE' )
INSERT INTO [ReportStyleElements] ([ReportStyleId], [ElementId], [ElementValue] )VALUES ( 2, 1, 'RED' )INSERT INTO [ReportStyleElements] ([ReportStyleId], [ElementId], [ElementValue] )VALUES ( 2, 2, 'YELLOW' )
This creates two report styles, one that has a BLUE header with WHITE text and another that has a RED header with YELLOW text.  In this example, I have overridden the TABLE_HEADER_BG and TABLE_HEADER_TEXT elements.  By not specifying the values for the other two, they will end up having the default values.
Now we have some styles defined, we need to have a query to allow those values to be available to the expressions of the report element’s properties. To do this, we need to PIVOT the row data to create a single row of columns so that this information is pulled once from the database and not for each element we need to style. Unfortunately the PIVOT syntax in SQL 2005 requires us to specify the columns manually so our query needs to be dynamic. The solution I present here uses a Stored Procedure to accomplish this task.
CREATE PROCEDURE up_ReportStyle (@ReportStyleId INT)AS
DECLARE @columns NVARCHAR(MAX);
SELECT @columns = STUFF(
(
    
SELECT ', ' + QUOTENAME(ElementName, '[') AS [text()]
    
FROM
        
(
      
SELECT e.[ElementName] , ISNULL(ElementValue,ElementDefaultValue) AS Value
        
FROM [Element] e WITH (NOLOCK)
        
LEFT JOIN [ReportStyleElements] rse WITH (NOLOCK) ON
        
rse.[ElementId] = e.[ElementId]
        
AND rse.[ReportStyleId] = @ReportStyleId) ReportStyleElements
        
FOR XML PATH ('')
        )
,
1, 1, '');
                      
DECLARE @sql NVARCHAR(MAX);SET @sql = 'SELECT ' + @columns + '
FROM (
SELECT e.[ElementName] , ISNULL(ElementValue,ElementDefaultValue) AS Value
FROM  [Element] e WITH (NOLOCK)
LEFT JOIN [ReportStyleElements] rse WITH (NOLOCK) ON
rse.[ElementId] = e.[ElementId] AND rse.[ReportStyleId] = '
+ CAST(@ReportStyleId AS VARCHAR (9)) + '

) AS ReportStyleElements
PIVOT ( MIN(Value) FOR ElementName IN ('
+ @columns + ')) AS [Elements]';
EXEC sp_ExecuteSQL @sql
GO
You can test this out within Management Studio by running...
-- The two styles we defined
EXEC up_ReportStyle @ReportStyleId = 1EXEC up_ReportStyle @ReportStyleId = 2
-- A style that doesn’t exist
EXEC up_ReportStyle @ReportStyleId = 3
As you can see it returns all the Elements, showing default values for those not defined by a report style. The final query shows all the defaults as the style supplied does not exist.
It is very easy to add this to a report .
Open or create a new report and create a new dataset. Specify the the Stored Procedure created earlier.
The ReportStyleId parameter will now be part of the report, when you deploy the report you can set this parameter and hide it so that you can have multiple linked versions of the same report each with its own styling. For the purpose of this article do not set a default for now as we want to see the styling change each time we run the report.
Either add or select an existing table in your report, clicking the texbox associated with the header. In the properties window find the BACKGROUNDCOLOR property and type the following into the value (or on the pull down menu select Expression and use the expression editor):
=Fields!TABLE_HEADER_BG.Value
Do the same for the COLOR property (this will be the colour of the text) and type:
=Fields!TABLE_HEADER_TEXT.Value
As you can see from the screenshot below, there is real no limit to how much you style your reports. Its also worth pointing out that it’s a single call to the database to get all of the elements.
When you run the report, you will be prompted for the ReportStyleId value.  Remember, earlier we created 2 Report Styles; either enter a value of 1 or 2 and click View Report. You will see the change in the background and text colour of the element that you styled. you can then change the report style by running the report again with different parameters.
You now have dynamic styling for your reports.

Conclusions

There are a few alternative solutions out there; Adam Aspin  has written several articles on the subject. He  has posted three articles on SQL Server Central which describe an interesting embedded code solution that use a database to store the styles much like my solution.  Instead of using a Stored Procedure, he uses the CLR and a compiled DLL that you use in your report project.  This would be a useful technique if you want to extend the logic of your styling or  if your business has standards which would require this kind of logic.  However, it operates on a 'per element' level; which means it makes a database call for every element that you need to style. All those round trips, merely to style a report, would pose an extra performance overhead.  Also, the use of a DLL would be an issue in IT departments that are concerned with the security issues of compiled DLLs.
If you want to read more about Adam’s work, please take a moment to read the full articles on SQL Server Central at http://www.sqlservercentral.com/Authors/Articles/Adam_Aspin/335295/.
As a closing thought, updating existing reports can be time consuming, but I've found it to be well worth the effort because it is so much easier to change the style. If you have a new report, one of the easiest ways to speed up the styling is to add your expressions to the elements in the first column of a TABLE (header, detail etc.) before creating additional columns to define your report.  When you add the additional columns they inherit the styling properties from the column you have already styled.