Available in SQL Server 2005/2008, the Pivot relationnal operator is used to convert rows into columns data. This feature is frequently used in reports and is pretty easy to work with.
SELECT productId,_year,amount FROM Products
We have this result :
productId _year amount 124 2001 125 125 2001 454 126 2001 75 127 2002 256 128 2004 457 129 2004 585 130 2002 142 131 2002 785 132 2005 452 133 2005 864 134 2005 762 135 2004 425 136 2003 452 137 2003 1024 138 2003 575
Now we are going to use PIVOT to have the years as columns and for a productId, the sum of amounts for 2001 and 2003 :
SELECT * FROM ( SELECT productId,_year,amount FROM Products )t PIVOT (SUM(amount) FOR _year IN ([2001],[2003])) AS pvt
So, we will have this result :
productId 2001 2003 124 125 NULL 125 454 NULL 126 75 NULL 127 NULL NULL 128 NULL NULL 129 NULL NULL 130 NULL NULL 131 NULL NULL 132 NULL NULL 133 NULL NULL 134 NULL NULL 135 NULL NULL 136 NULL 452 137 NULL 1024 138 NULL 575
Ok, that’s nice. But if we consider that we don’t know the names of the columns, we have to make our PIVOT dynamic. Look at the following code :
We are first going to build a string that concatenes all years
DECLARE @years VARCHAR(2000) SELECT @years = STUFF(( SELECT DISTINCT '],[' + ltrim(str(_year)) FROM Products ORDER BY '],[' + ltrim(str(YEAR(_year))) FOR XML PATH('') ), 1, 2, '') + ']'
So this string will contain all years needed for our PIVOT query:
[2001],[2002],[2003],[2004],[2005]
After that, all we have to do is to dynamically create our PIVOT query. Here is the complete query :
DECLARE @query VARCHAR(4000) DECLARE @years VARCHAR(2000) SELECT @years = STUFF(( SELECT DISTINCT '],[' + ltrim(str(_year)) FROM Products ORDER BY '],[' + ltrim(str(YEAR(_year))) FOR XML PATH('') ), 1, 2, '') + ']' SET @query = 'SELECT * FROM ( SELECT productId,_year,amount FROM Products )t PIVOT (SUM(amount) FOR _year IN ('+@years+')) AS pvt' EXECUTE (@query)
And here is the displayed result :
productId 2001 2002 2003 2004 2005 124 125 NULL NULL NULL NULL 125 454 NULL NULL NULL NULL 126 75 NULL NULL NULL NULL 127 NULL 256 NULL NULL NULL 128 NULL NULL NULL 457 NULL 129 NULL NULL NULL 585 NULL 130 NULL 142 NULL NULL NULL 131 NULL 785 NULL NULL NULL 132 NULL NULL NULL NULL 452 133 NULL NULL NULL NULL 864 134 NULL NULL NULL NULL 762 135 NULL NULL NULL 425 NULL 136 NULL NULL 452 NULL NULL 137 NULL NULL 1024 NULL NULL 138 NULL NULL 575 NULL NULL
Enjoy 😉
PS : You might have this error message when you run the query :
Incorrect syntax near ‘PIVOT’. You may need to set the compatibility level of the current database to a higher value to enable this feature. See help for the stored procedure sp_dbcmptlevel.
So, to enable this feature, you have to set the compatibility level of your database to a higher level by using the following stored procedure :
--If you are running SQL 2005 EXEC sp_dbcmptlevel 'myDatabaseName', 90 --If you are running SQL 2008 EXEC sp_dbcmptlevel 'myDatabaseName', 100
This is SO well done – Great Job!
This is great. How would it be achieved in a view?
Create Store Procedure and call from View using “EXEC STOREPROCEDURENAME”
Awesome !!!
I really Like it…I have Small Question. Will we be able to store the retrieved pivot data into a new Table? if so How?
Suman – you would add to the dynamic code:
‘INSERT INTO #TableName
SELECT * FROM…
I hope that helps.
I have a question. I copied the code exactly and I keep receiving an error message:
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
This isn’t making sense to me because the same code exists for both the select distinct and the order by.
Any ideas?
I found it..Its very Basic..But took time to figure it out..
DECLARE @query VARCHAR(4000)
DECLARE @years VARCHAR(2000)
SELECT @years = STUFF(( SELECT DISTINCT
‘],[‘ + ltrim(str(_year))
FROM Products
ORDER BY ‘],[‘ + ltrim(str(YEAR(_year)))
FOR XML PATH(”)
), 1, 2, ”) + ‘]’
SET @query =
‘SELECT *
INTO dbo.TmpTable
FROM
(
SELECT productId,_year,amount
FROM Products
)t
PIVOT (SUM(amount) FOR _year
IN (‘+@years+’)) AS pvt’
EXECUTE (@query)
We can use, Select * into tablename From table Will retrieve the data into Permanant table…
WoW Is great Just Need
Nevermind on the error message. The code above has on extra ) in it by the ORDER BY line.
Suman, I tried that and I just completed an into after the select statement and before the FROM. The problem is, then I couldn’t get the data out of the temporary table. I haven’t tried it yet, but I think if you create it as a global temporary table you might be able to do so.
My question is why use STUFF when creating the dynamic columns?
Thanks for the tutorial! Came in handy.
Suman Balguri, It’s probably a bit late but you just add INTO after SELECT *
For example:
SET @query =
‘SELECT * INTO AnotherTable FROM
(
SELECT productId,_year,amount
FROM Products
)t
PIVOT (SUM(amount) FOR _year
IN (‘+@years+’)) AS pvt’
EXECUTE (@query)
Nice solution, but I receive two errors when using it with a large table: Msg 8114, error converting datatype nvarchar to bigint, and Msg 473, incorrect value **** provided to PIVOT operator (my translation). Any suggestions?
That’s amazing!!!
Thank you very much for sharing it!
you’re so smart. Well done!
It works great, but I am having some difficulty adding in a where clause for a date value… comments?
Brilliant! Now solve one additional problem: Where you have years, I have INTs: 1, 2, 3, 4, …., 1000. To be useful, I must have the columns sort in numerical order instead of lexicographic order, i.e. [1], [10], [100], [11], [110], [111],…. I suppose I could update my INTs to CHAR padded with zeros….
How we can use this pivot result in our c# code using linq?
Everything is ok..except that i received an error like this:
Incorrect syntax near ‘+@years+’.
How can i solve this error.
I love it
that’s gr8… but can u help me if we have more columns in the table apart from Amount in ur example… how to display the other column data like u displayed for amount…
thanx in advance..
Is it possible to have two columns per pivot?
on the y-axe we want the customer-Number, on the x-axe the articles
we will sum the account and the number of pieces
1001001 1002002 1003003……..
Art. 100101 100,00$ 20 200,00$ 30 300$ 40
Msg 245, Level 16, State 1, Line 115
Conversion failed when converting the varchar value ‘],[‘ to data type int.
Nice article this shows how microsoft consistently makes things 100 times harder then they need to be. A much better implementation would be the following.
…..
PIVOT (SUM(amount) FOR _year
year IN ( select years from somevalue)) AS pvt
As the solution proposed above has performance issues like all dynamic sql amongst a host of other problems if used in more real world examples.
Why does everyone give Microsoft a free pass and keep coming up with hackish type work arounds instead of demanding they give us better products. Developers continue to tolerate garbage so we get garbage tools. Just because we can push car from point A to point B doesn’t mean we should.
awesome thank you so much!
getting my data in a dynamic table made me so happy!!
Can this be done without using the @query variable and the execute command?
Excellent. Help me to solve dynamic columns in pivot table. Thanks!