Monday, September 8, 2008

Create a cross-tab (or "pivot") query in SQL

Cross-tab (or "pivot") query in SQL

You may find that your data is not well-suited for what you might term as pivot or cross-tab reports. Now, while I believe that such pivoting functionality belongs at the presentation tier and not the data tier, there are many ways to generate reports like this. Here is a simple example that takes normalized sales figures by year and quarter, and produces one row and four columns per year. Run this in Query Analyzer and the output should be pretty self-explanatory:

SET ANSI_WARNINGS OFF
SET NOCOUNT ON

CREATE TABLE dbo.SalesByQuarter
(
Y INT,
Q INT,
sales INT,
PRIMARY KEY (Y,Q)
)
GO

INSERT dbo.SalesByQuarter(Y,Q,Sales)
SELECT 2003, 2, 479000
UNION SELECT 2003, 3, 321000
UNION SELECT 2003, 4, 324000
UNION SELECT 2004, 1, 612000
UNION SELECT 2004, 2, 524000
UNION SELECT 2004, 3, 342000
UNION SELECT 2004, 4, 357000
UNION SELECT 2005, 1, 734000
GO

SELECT Y,
Q1 = SUM(CASE WHEN Q=1 THEN Sales END),
Q2 = SUM(CASE WHEN Q=2 THEN Sales END),
Q3 = SUM(CASE WHEN Q=3 THEN Sales END),
Q4 = SUM(CASE WHEN Q=4 THEN Sales END)
FROM
dbo.SalesByQuarter
GROUP BY Y
ORDER BY Y
GO

DROP TABLE dbo.SalesByQuarter
GO


SQL Server 2005 introduces the new PIVOT keyword, however I think it will be a disappointment to most. What people have been searching for is a very dynamic way to pivot, so that you don't have to know all of the potential column headers beforehand. Again, I still feel that the best place to rotate this data is on the client. But I know that some people out there are stubborn and/or don't understand the difference.

In any case, here is the above query in SQL Server 2005 style:

CREATE TABLE dbo.SalesByQuarter
(
Y INT,
Q INT,
sales INT,
PRIMARY KEY (Y,Q)
)
GO

INSERT dbo.SalesByQuarter(Y,Q,Sales)
SELECT 2003, 2, 479000
UNION SELECT 2003, 3, 321000
UNION SELECT 2003, 4, 324000
UNION SELECT 2004, 1, 612000
UNION SELECT 2004, 2, 524000
UNION SELECT 2004, 3, 342000
UNION SELECT 2004, 4, 357000
UNION SELECT 2005, 1, 734000
GO

SELECT Y,
[1] AS Q1,
[2] AS Q2,
[3] AS Q3,
[4] AS Q4
FROM
(SELECT Y, Q, Sales
FROM SalesByQuarter) s
PIVOT
(
SUM(Sales)
FOR Q IN ([1],[2],[3],[4])
) p
ORDER BY [Y]
GO

DROP TABLE dbo.SalesByQuarter
GO