in
Eric Vaillancourt on SQL Server...

MS SQL Dev

April 2008 - Posts

  • Pivots with Dynamic Columns in SQL Server 2005/2008

    Pivots in SQL Server 2005/2008 can convert row into column data. Pivots are frequently used in reports, and are reasonably easy to work with.

     

    However, many people have asked me how to make the column list dynamic.  Normally, this list is fixed, but many times the new columns are determined by the data at a later stage. This problem is easily solved when we mix pivots with dynamic SQL, so here is a very simple example about how to dynamically generate the pivot statement:

     

    PIVOT allows you to turn data rows into columns. For example, if you have a query like this:

     

    USE AdventureWorks

    GO

    SELECT * FROM

          (SELECT CustomerID, DATEPART(m, OrderDate) OrderMonth, SubTotal

                      FROM Sales.SalesOrderHeader

                      WHERE OrderDate between '20030101' and '20031231'

                      and CustomerID IN (2,4,6,7,8,9)) src

    PIVOT (SUM(SubTotal) FOR OrderMonth

    IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) AS pvt

    GO

     

    clip_image002

     

    The rows coming from the internal query are transposed into colums:

     

    SELECT * FROM

          (SELECT CustomerID, DATEPART(m, OrderDate) OrderMonth, SubTotal

                      FROM Sales.SalesOrderHeader

                      WHERE OrderDate between '20030101' and '20031231'

                      and CustomerID IN (2,4,6,7,8,9)) src

    PIVOT (SUM(SubTotal) FOR OrderMonth

    IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) AS pvt

     

    This is ok if the colum list suppied in the IN clause is fixed like the 12 months of the year.  But if the list evolves with the data, you would have to re-write your pivot.  Not to practical.

     

    Let’s look at another example:

     

    SELECT * FROM

          (SELECT CustomerID, YEAR(OrderDate) OrderYear, SubTotal

                FROM Sales.SalesOrderHeader

                WHERE CustomerID >=1 and CustomerID <=35) src

    PIVOT (SUM(SubTotal) FOR OrderYear

    IN ([2002],[2003])) AS pvt

    GO

     

    In this pivot example, we want to display a colums for each year and a row for each customer:

     

    clip_image004

     

    Notice that we are only displaying data for years 2002 and 2003.  We could modify our pivot to make it dynamic.

     

    Look at the following code:

     

    DECLARE @listCol VARCHAR(2000)

    SELECT  @listCol = STUFF(( SELECT DISTINCT

                                    '],[' + ltrim(str(YEAR(OrderDate)))

                            FROM    Sales.SalesOrderHeader

                            ORDER BY '],[' + ltrim(str(YEAR(OrderDate)))

                            FOR XML PATH('')

                                        ), 1, 2, '') + ']'

     

    By using the combination of STUFF and FOR XML PATH we can build a string that concatenates all columns by doing a SELECT DISTINCT on our table before doinf the pivot.

     

    After executing this piece of code, you should get in the @listCol variable the list of colums needed to do the pivot.

     

    clip_image006

     

    Now, all we have to do is to convert our pivot into a dynamic query:

     

    DECLARE @listCol VARCHAR(2000)

    DECLARE @query VARCHAR(4000)

    SELECT  @listCol = STUFF(( SELECT DISTINCT

                                    '],[' + ltrim(str(YEAR(OrderDate)))

                            FROM    Sales.SalesOrderHeader

                            ORDER BY '],[' + ltrim(str(YEAR(OrderDate)))

                            FOR XML PATH('')

                                        ), 1, 2, '') + ']'

     

    SET @query =

    'SELECT * FROM

          (SELECT CustomerID, YEAR(OrderDate) OrderYear, SubTotal

                FROM Sales.SalesOrderHeader

                WHERE CustomerID >=1 and CustomerID <=35) src

    PIVOT (SUM(SubTotal) FOR OrderYear

    IN ('+@listCol+')) AS pvt'

     

    EXECUTE (@query)

     

    Et voila:

     

    clip_image008

     

     

    Hope this was helpful,

     

    Eric Vaillancourt

    www.sqlprof.com

     

  • Functions to Work with NULL Values

    Today I was asked what was the replacement for the IIF command in MS Access and how to manage nulls in SQL Server.  So I decided to write about nulls, not that I like to work with them...but sometimes we don't have a choice ;)

     

    SQL Server may generate unexpected results when performing calculations on columns that have NULL values. So, to resolve this problem you need to use the ISNULL, NULLIF, and COALESCE functions of SQL Server 2005 when your columns hold NULL values. 

     

    ISNULL: The ISNULL function replaces NULL values with the specified replacement value. The following is the syntax of the ISNULL function:

    ISNULL ( check_expression , replacement_value )

     

    The function takes the following arguments:

    • check_expression is the expression to be checked for NULL values and can be of any data type.

    • replacement_value is the expression to be returned if check_expression is NULL and should be of the same data type as the check_expression.

    The following code example demonstrates the use of the ISNULL function in a SELECT statement.

     

    USE Adventureworks;

    SELECT AVG(ISNULL(Weight, 60))

    FROM Production.Product

     

    In this example, the ISNULL function is used to find the average of the weight of all products. The ISNULL function substitutes the value 60 for all NULL entries in the Weight column of the Product table.

     

    NULLIF: The NULLIF function returns a NULL value if the two specified expressions are equivalent. The following is the syntax of the NULLIF function:

    NULLIF (expr , expr)

     

    The function takes two arguments. The argument, expression, can be a constant, column name, function, subquery, or any combination of arithmetic, bitwise, and string operators. The NULLIF function returns the result that is of the same data type as the first expression.

    The NULLIF function returns the first expression if the two expressions are not equivalent. Otherwise, it returns a NULL value.

    The following code example retrieves the average StandardCost of products that belong to the Class ‘M’.

     

    USE Adventureworks;

    GO

    SELECT AVG (StandardCost) AS AvgCost

    FROM Production.Product

    WHERE Class='M'

     

    This query returns an average of 378.5403 that includes some rows that have NULL values in the StandardCost column. Therefore, the average cost value that is displayed in the result set is incorrect, it should have been 402.199. However, the same query can be rewritten with the help of the NULLIF function to ensure that the products with NULL as StandardCost are not included in the calculation.

     

    USE Adventureworks;

    SELECT AVG(NULLIF(StandardCost, 0)) AS AvgCost

    FROM Production.Product

    WHERE Class='M'

     

    COALESCE: The COALESCE function returns the first non-null expression among its arguments. The following is the syntax of the COALESCE function:

    COALESCE ( expression [ ,...n ] )

     

    The function takes two arguments. The argument expression is an expression of any data type. The argument n is a placeholder indicating that multiple expressions can be specified. All expressions must be of the same data type.

    The COALESCE function returns the result that is of the same data type as the expression. If all arguments of the COALESCE function are NULL, then it returns NULL.

    The ISNULL function replaces NULL values with the specified replacement value. The following is the syntax of the ISNULL function:

    The following example uses the COALESCE function to return the first non-null telephone number for a particular customer and for customers with no valid telephone numbers, the string 'No number on file' is displayed.

     

    SELECT CustomerName, COALESCE(CellPhone, OfficePhone, HomePhone, 'No number on file') as Telephone FROM Customer

     

    Hope this helps,

     

    Eric Vaillancourt

    http://www.sqlprof.com

     

    Technorati : ,,

  • How to Use Aggregate Functions with NULL Values

    Effect of NULL Values on Aggregate Functions

    NULL values may cause aggregate functions to produce unexpected or incorrect results because these functions ignore NULL values for calculations. For example, if the Weight column of a table has 504 records, of which 299 records have NULL values, the average of this column calculated by using the AVG function may return incorrect values.

    The following code example demonstrates this:

    USE AdventureWorks

    GO

    SELECT AVG(Weight) AS 'Average Weight'

    FROM Production.Product

     

    When we execute the query, we get an average weight of 74.069219.  We get this result because when calculating the average, the AVG function considers only the 205 rows that contain non-NULL values. However, it is expected that the function considers all the 504 records.

     

    The ISNULL Function to the rescue

     

    You can correct this problem by using the ISNULL function, as shown in the following code example:

     

    SELECT AVG(ISNULL(Weight,0)) AS 'Average Weight'

    FROM Production.Product

     

    Now we get the right result of 30.127361.

     

    Effect of NULL Values on the COUNT(*) Function

     

    The COUNT(*) function is an exception to this problem because it returns the total number of records in a table, irrespective of the NULL values, as shown in the following code example:

     

    SELECT COUNT(*) AS 'COUNT'

    FROM Production.Product

     

    When we execute this query, we get the right count of 504.

     

    Hope this helps,

    Eric Vaillancourt

    http://www.sqlprof.com

     

©2008 SQLProf.com & Eric Vaillancourt
Powered by Community Server (Non-Commercial Edition), by Telligent Systems