Understanding PIVOT function in T-SQL - sql-server

I am very new to SQL.
I have a table like this:
ID | TeamID | UserID | ElementID | PhaseID | Effort
-----------------------------------------------------
1 | 1 | 1 | 3 | 5 | 6.74
2 | 1 | 1 | 3 | 6 | 8.25
3 | 1 | 1 | 4 | 1 | 2.23
4 | 1 | 1 | 4 | 5 | 6.8
5 | 1 | 1 | 4 | 6 | 1.5
And I was told to get data like this
ElementID | PhaseID1 | PhaseID5 | PhaseID6
--------------------------------------------
3 | NULL | 6.74 | 8.25
4 | 2.23 | 6.8 | 1.5
I understand I need to use PIVOT function. But can't understand it clearly.
It would be great help if somebody can explain it in above case.(or any alternatives if any)

A PIVOT used to rotate the data from one column into multiple columns.
For your example here is a STATIC Pivot meaning you hard code the columns that you want to rotate:
create table temp
(
id int,
teamid int,
userid int,
elementid int,
phaseid int,
effort decimal(10, 5)
)
insert into temp values (1,1,1,3,5,6.74)
insert into temp values (2,1,1,3,6,8.25)
insert into temp values (3,1,1,4,1,2.23)
insert into temp values (4,1,1,4,5,6.8)
insert into temp values (5,1,1,4,6,1.5)
select elementid
, [1] as phaseid1
, [5] as phaseid5
, [6] as phaseid6
from
(
select elementid, phaseid, effort
from temp
) x
pivot
(
max(effort)
for phaseid in([1], [5], [6])
)p
Here is a SQL Demo with a working version.
This can also be done through a dynamic PIVOT where you create the list of columns dynamically and perform the PIVOT.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.phaseid)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT elementid, ' + #cols + ' from
(
select elementid, phaseid, effort
from temp
) x
pivot
(
max(effort)
for phaseid in (' + #cols + ')
) p '
execute(#query)
The results for both:
ELEMENTID PHASEID1 PHASEID5 PHASEID6
3 Null 6.74 8.25
4 2.23 6.8 1.5

These are the very basic pivot example kindly go through that.
SQL SERVER – PIVOT and UNPIVOT Table Examples
Example from above link for the product table:
SELECT PRODUCT, FRED, KATE
FROM (
SELECT CUST, PRODUCT, QTY
FROM Product) up
PIVOT (SUM(QTY) FOR CUST IN (FRED, KATE)) AS pvt
ORDER BY PRODUCT
renders:
PRODUCT FRED KATE
--------------------
BEER 24 12
MILK 3 1
SODA NULL 6
VEG NULL 5
Similar examples can be found in the blog post Pivot tables in SQL Server. A simple sample

Ive something to add here which no one mentioned.
The pivot function works great when the source has 3 columns: One for the aggregate, one to spread as columns with for, and one as a pivot for row distribution. In the product example it's QTY, CUST, PRODUCT.
However, if you have more columns in the source it will break the results into multiple rows instead of one row per pivot based on unique values per additional column (as Group By would do in a simple query).
See this example, ive added a timestamp column to the source table:
Now see its impact:
SELECT CUST, MILK
FROM Product
-- FROM (SELECT CUST, Product, QTY FROM PRODUCT) p
PIVOT (
SUM(QTY) FOR PRODUCT IN (MILK)
) AS pvt
ORDER BY CUST
In order to fix this, you can either pull a subquery as a source as everyone has done above - with only 3 columns (this is not always going to work for your scenario, imagine if you need to put a where condition for the timestamp).
Second solution is to use a group by and do a sum of the pivoted column values again.
SELECT
CUST,
sum(MILK) t_MILK
FROM Product
PIVOT (
SUM(QTY) FOR PRODUCT IN (MILK)
) AS pvt
GROUP BY CUST
ORDER BY CUST
GO

A pivot is used to convert one of the columns in your data set from rows into columns (this is typically referred to as the spreading column). In the example you have given, this means converting the PhaseID rows into a set of columns, where there is one column for each distinct value that PhaseID can contain - 1, 5 and 6 in this case.
These pivoted values are grouped via the ElementID column in the example that you have given.
Typically you also then need to provide some form of aggregation that gives you the values referenced by the intersection of the spreading value (PhaseID) and the grouping value (ElementID). Although in the example given the aggregation that will be used is unclear, but involves the Effort column.
Once this pivoting is done, the grouping and spreading columns are used to find an aggregation value. Or in your case, ElementID and PhaseIDX lookup Effort.
Using the grouping, spreading, aggregation terminology you will typically see example syntax for a pivot as:
WITH PivotData AS
(
SELECT <grouping column>
, <spreading column>
, <aggregation column>
FROM <source table>
)
SELECT <grouping column>, <distinct spreading values>
FROM PivotData
PIVOT (<aggregation function>(<aggregation column>)
FOR <spreading column> IN <distinct spreading values>));
This gives a graphical explanation of how the grouping, spreading and aggregation columns convert from the source to pivoted tables if that helps further.

SELECT <non-pivoted column>,
[first pivoted column] AS <column name>,
[second pivoted column] AS <column name>,
...
[last pivoted column] AS <column name>
FROM
(<SELECT query that produces the data>)
AS <alias for the source query>
PIVOT
(
<aggregation function>(<column being aggregated>)
FOR
[<column that contains the values that will become column headers>]
IN ( [first pivoted column], [second pivoted column],
... [last pivoted column])
) AS <alias for the pivot table>
<optional ORDER BY clause>;
USE AdventureWorks2008R2 ;
GO
SELECT DaysToManufacture, AVG(StandardCost) AS AverageCost
FROM Production.Product
GROUP BY DaysToManufacture;
DaysToManufacture AverageCost
0 5.0885
1 223.88
2 359.1082
4 949.4105
-- Pivot table with one row and five columns
SELECT 'AverageCost' AS Cost_Sorted_By_Production_Days,
[0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost
FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN ([0], [1], [2], [3], [4])
) AS PivotTable;
Here is the result set.
Cost_Sorted_By_Production_Days 0 1 2 3 4
AverageCost 5.0885 223.88 359.1082 NULL 949.4105

To set Compatibility error
use this before using pivot function
ALTER DATABASE [dbname] SET COMPATIBILITY_LEVEL = 100

FOR XML PATH might not work on Microsoft Azure Synapse Serve. A possible alternative, following #Taryn dynamic generated cols approach, same results is obtained by using STRING_AGG.
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STRING_AGG(QUOTENAME(c.phaseid),', ')
/*OPTIONAL: within group (order by cast(t1.[FLOW_SP_SLPM] as INT) asc)*/
FROM (SELECT phaseid FROM temp
GROUP BY phaseid) c
set #query = 'SELECT elementid,' + #cols + ' from
(
select elementid,
phaseid,
effort
from temp
) x
PIVOT
(
max(effort)
for phaseid in (' + #cols + ')
) p '
execute(#query)

Related

Automatically generate columns name in CTE using SQL Server

I am building a pivot query inside a CTE. I have a table Table_1:
Store Week xCount
------- ---- ------
101 1 138
105 1 37
109 1 59
101 2 282
109 2 97
105 3 60
109 3 87
This is the query I used to pivot Table_1:
with CTE as
(
select
*
from
(select
store, week, xCount
from
table_1) src
pivot
(sum(xcount)
for week in ([1], [2], [3])
) piv;
)
Select *
From CTE
And this is the result I got:
| STORE | 1 | 2 | 3 |
+-------+-----+-----+-----+
| 101 | 138 | 282 | null|
| 105 | 37 | null| 60 |
| 109 | 59 | 97 | 87 |
The result is fine, but now there is one more WEEK added.
I want to develop a CTE with pivot query that will automatically generate distinct weeks and create a column on that basis.
I did some research and found a recursive CTE can be used to do this. I am new to recursive CTE, so please anyone can help me to solve this issue.
I also tried dynamic pivot query but CTE does not allow dynamic query.
Please help.
dynamic pivot doesn't work inside CTE
No, but a CTE works inside a dynamic query:
{assuming you have declared the variables used below}
SELECT #Cols = {query to get the column names in a comma-separated string}
SET #sql='
with CTE as
(
select
*
from
(select
store, week, xCount
from
table_1) src
pivot
(sum(xcount)
for week in ('+#Cols+')
) piv;
)
Select *
From CTE
'
EXEC (#sql)
Can i use recursive CTE?
No this isn't an appropriate use-case for a recursive CTE.
/* Variable to hold unique Week to be used in PIVOT clause */
DECLARE #Weeks NVARCHAR(MAX) = N''
/* Extract unique Week names with pivot formattings */
SELECT #Weeks = #Weeks + ', [' + COALESCE(week, '') + ']'
FROM (SELECT DISTINCT week FROM table_1) DT
/* Remove first comma and space */
SELECT #Weeks = LTRIM(STUFF(#Weeks , 1, 1, ''))
/* Variable to hold t-sql query */
DECLARE #CTEStatement NVARCHAR(MAX) = N''
/* Generate dynamic PIVOT query here */
SET #CTEStatement=N'
;WITH CTE as
( SELECT *
FROM
(SELECT
store
,week
,xCount
FROM
table_1) SRC
PIVOT
(SUM(xcount)
FOR week in ('+ #Weeks +')
) PIV;
)
SELECT *
FROM CTE
'
EXEC (#CTEStatement)

Converting rows to columns

I have a query with the columns 'Name', 'Amount', and 'ReasonId'. I want to sum the amount and put the reasons on one row to keep every name to a single line. There are about 50 distinct ReasonId's so I do not want to name the column the name of the ReasonId's. Instead, I would like to name the columns 'Reason1', 'Reason2', 'Reason3', and 'Reason4'. One single name can have up to 4 different reasons.
I have this:
Name Amount ReasonId
-------------------------
Bob $5 7
Bob $8 6
John $2 8
John $5 9
John $3 9
John $8 4
I want to produce the following:
Name Amount Reason1 Reason2 Reason3 Reason4
-----------------------------------------------------
Bob $13 7 6 NULL NULL
John $18 8 9 4 NULL
One way to do this is to use the dense_rank window function to number the rows, and then use conditional aggregation to put the reason in the correct columns.
I can't see anything that would give the specific order of the reason columns though, maybe there is some column missing that provides the order?
with cte as (
select
name,
reasonid,
amount,
dense_rank() over (partition by name order by reasonid) rn
from your_table
)
select
name,
sum(amount) amount,
max(case when rn = 1 then reasonid end) reason1,
max(case when rn = 2 then reasonid end) reason2,
max(case when rn = 3 then reasonid end) reason3,
max(case when rn = 4 then reasonid end) reason4
from cte
group by name
If you have some column that gives the order you want then change the order by clause used in the dense_rank function.
Sample SQL Fiddle (using PG as MSSQL seems to be offline).
The output from the query above would be:
| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
| Bob | 13 | 6 | 7 | (null) | (null) |
| John | 18 | 4 | 8 | 9 | (null) |
You could also use a pivot to achieve this; if you know the columns you can enter them in the script, but if not, you can use dynamic sql (there are reasons why you might want to avoid the dynamic solution).
The advantage of this route is that you can enter the column list in a table and then changes to that table will result in changes to your output with change to the script involved. The disadvantages are all those associated with dynamic SQL.
In the interests of variation, here is a dynamic SQL solution using temp tables to hold your data, since a different possibility has been provided:
-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))
INSERT #AllPossibleReasons
VALUES
(1,'Reason1')
,(2,'Reason2')
,(3,'Reason3')
,(4,'Reason4')
,(5,'Reason5')
,(6,'Reason6')
,(7,'Reason7')
,(8,'Reason8')
,(9,'Reason9')
INSERT #MyTab
VALUES
('Bob',7,7)
,('Bob',8,6)
,('John',2,8)
,('John',5,9)
,('John',3,9)
,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE #ReasonList VARCHAR(MAX) = ''
DECLARE #SQL VARCHAR(MAX)
SELECT #ReasonList = #ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET #ReasonList = SUBSTRING(#ReasonList,2,LEN(#ReasonList))
SET #SQL =
'SELECT Name,Value,' + #ReasonList + ' FROM
(SELECT
M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
FROM
#MyTab AS M
INNER JOIN #AllPossibleReasons AS Reason ON M.ReasonId = Reason.Id
INNER JOIN(SELECT T.Name, SUM(Amount)Value
FROM #MyTab T GROUP BY T.Name) AS Total ON M.Name = Total.Name
GROUP BY M.Name, Reason.Label) AS Up
PIVOT (SUM(THis) FOR Label IN (' + #ReasonList + ')) AS Pvt'
EXEC (#SQL)
DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab
Working from the information in ListAGG in SQLSERVER, I came up with this somewhat ugly example:
with tbl1 as (
-- Set up initial data set
select 'Bob' name, 5 amount, 7 ReasonId
union all select 'Bob' , 3, 4
union all select 'Bob', 2, 1
union all select 'Brian', 8, 2
union all select 'Bob', 6, 4
union all select 'Brian', 1, 3
union all select 'Tim', 2, 2)
, TBL2 AS ( -- Add a blank to separate the concatenation
SELECT NAME
, AMOUNT
, CAST(ReasonId as varchar) + ' ' ReasonId from tbl1
)
select ta.name
, Total
, ReasonIds from (
(select distinct name, stuff((select distinct '' + t2.ReasonId from tbl2 t2
where t1.name = t2.name
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;
This converts TBL1 to the following:
name Total ReasonIds
Bob 16 1 4 7
Brian 9 2 3
Tim 2 2

Turning string into rows

I have an old vintage system with a table looking like this.
OptionsTable
id options
=== ========================
101 Apple,Banana
102 Audi,Mercedes,Volkswagen
In the application that consumes the data, a function will break down the options column into manageable lists and populate dropdowns etc.
The problem is that this kind of data isn't very SQL friendly, making it difficult to make ad-hoc queries and reports.
To that end, I'd like to transform the data into a friendlier view, looking like this:
OptionsView
id name value
=== ========== =====
101 Apple 1
101 Banana 2
102 Audi 1
102 Mercedes 2
102 Volkswagen 3
Now, there have been some topics on splitting string into rows in t-sql (Turning a Comma Separated string into individual rows comes to mind), but apart from splitting the strings into rows, I also need to generate values based on the position in the string.
The plan is to make a view that hides the uglines of the original table.
It will be used in a join with the table housing the answers in order to make ad-hoc statistical queries.
Is there a good way of doing this without having to use cursors etc?
Perhaps adding a udf is overkill for your needs, but I created a split function a long time ago that returns the value, the startposition within the string and the index. With it, the usage in this scenario would be:
select id, String as [Name], ItemIndex as value from OptionsTable
outer apply dbo.Split(options, ',')
Results:
id Name value
101 Apple 1
101 Banana 2
102 Audi 1
102 Mercedes 2
102 Volkswagen 3
And the split function (unrevised since then):
ALTER function [dbo].[Split] (
#StringToSplit varchar(2048),
#Separator varchar(128))
returns table as return
with indices as
(
select 0 S, 1 E, 0 I
union all
select E, charindex(#Separator, #StringToSplit, E) + len(#Separator) , I + 1
from indices
where E > S
)
select substring(#StringToSplit,S,
case when E > len(#Separator) then e-s-len(#Separator) else len(#StringToSplit) - s + 1 end) String
,S StartIndex, I ItemIndex
from indices where S >0
This should work for you:
DECLARE #OptionsTable TABLE
(
id INT
, options VARCHAR(100)
);
INSERT INTO #OptionsTable (id, options)
VALUES (101, 'Apple,Banana')
, (102, 'Audi,Mercedes,Volkswagen');
SELECT OT.id, T.name, t.value
FROM #OptionsTable AS OT
CROSS APPLY (
SELECT T.column1, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM dbo.GetTableFromList(OT.options, ',') AS T
) AS T(name, value);
Here dbo.GetTableFromList is a split string function.
CROSS APPLY executes this function for each row resulting in options split into names in seperate rows. And I used ROW_NUMBER() to add value row, If you want to order result set by name, please use ROW_NUMBER() OVER (ORDER BY t.column1), that should and probably will make results look consistent all the time.
Result:
id name value
-----------------
101 Apple 1
101 Banana 2
102 Audi 1
102 Mercedes 2
102 Volkswagen 3
You could convert your string to XML and then parse the string to transpose it to rows something like this:
SELECT A.[id]
,Split.a.value('.', 'VARCHAR(100)') AS Name
,ROW_NUMBER() OVER (PARTITION BY [id] ORDER BY (SELECT NULL)) as Value
FROM (
SELECT [id]
,CAST('<M>' + REPLACE([options], ',', '</M><M>') + '</M>' AS XML) AS Name
FROM optionstable
) AS A
CROSS APPLY Name.nodes('/M') AS Split(a);
Credits: #SRIRAM
SQL Fiddle Demo

How to retrieve multiple detail records plus the master record as a single row?

I have two tables with a structure similar to this:
Person:
ID Name Age
1 Jack 25
2 Jill 23
Tests:
ID PersonID TestID Result
1 1 1 125
2 1 2 120
3 1 3 75
4 2 1 90
5 2 2 95
6 2 3 7.2
Is there a way to retrieve that data with a single statement in a way that each record in the master table is presented in a single row? Something like this:
PersonID Name Age Test1 Test2 Test3
1 Jack 25 125 120 75
2 Jill 23 90 95 7.2
So far, the only way I have come up with has been to create a function which iterates through the detail records and fills a temporary table. Not very elegant.
Thanks in advance
In order to get this result, you will need to use the PIVOT function. This transforms the data from multiple rows into columns.
If you know the values ahead of time or you will have a limited number of TestId values, then you can hard-code the query making the query static.
SELECT Name,
Age,
[1] AS Test1,
[2] AS Test2,
[3] AS Test3
FROM
(
SELECT P.Name, P.Age, t.TestID, t.Result
FROM tests t
INNER JOIN person P
ON p.ID = t.PersonID
) T
PIVOT
(
sum(Result)
FOR TestID IN ([1], [2], [3])
) piv;
See SQL Fiddle with Demo.
But if you have an unknown number of TestId values, then you will want to use dynamic SQL to generate the list of columns at run-time. You Code will be:
DECLARE #cols AS NVARCHAR(MAX),
#colNames AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(testId)
from tests
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colNames = STUFF((SELECT distinct ',' + QUOTENAME(testId) +' as Test'+cast(testId as varchar(10))
from tests
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Name, age, ' + #colnames + ' from
(
select P.Name, P.Age, t.TestID, t.Result
from tests t
inner join person P
on p.ID = t.PersonID
) x
pivot
(
sum(Result)
for TestID in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
They both will generate the same result, difference being that the dynamic one will increase/decrease the columns if the number of test ids changes:
| NAME | AGE | TEST1 | TEST2 | TEST3 |
--------------------------------------
| Jack | 25 | 125 | 120 | 75 |
| Jill | 23 | 90 | 95 | 7.2 |
You can do a pivot on the TestID
Here you go... its kinda messy but you can improve on it :)
SELECT Name,Age,SUM([1]) AS Test1,SUM([2]) AS Test2,SUM([3]) AS Test3
FROM(
SELECT P.Name,P.Age,Te.ID, TestID,Result
FROM Test Te
INNER JOIN dbo.Person P ON P.ID=Te.PersonID) T
PIVOT(MAX(T.Result) FOR TestID IN([1],[2],[3])) AS pvt
GROUP BY Name,Age
heres some links
http://msdn.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
http://www.codeproject.com/Questions/393632/How-to-use-Pivot-in-SQL
http://blog.sqlauthority.com/2008/06/07/sql-server-pivot-and-unpivot-table-examples/

SQL Server Pivots: Displaying row values to column headers

I have a table (items) which is in the following format:
ITEMNO | WEEKNO | VALUE
A1234 | 1 | 805
A2345 | 2 | 14.50
A3547 | 2 | 1396.70
A2208 | 1 | 17.65
A4326 | 6 | 19.99
It's a table which shows the value of sales for items in a given week.
The results or what I want to display in a table format is the item number in a row followed by columns for each week containing the values, e.g.
ITEMNO | WK1 | WK2 | WK3 | WK4 | WK5 ...etc up to 52
A1234 | 805 | 345 | 234 | 12 | 10 ...etc up to 52
A2345 | 23 | 12 | 456 | 34 | 99 ...etc up to 52
A3456 | 234 | 123 | 34 | 25 | 190 ...etc up to 52
Although I've 52...so I've only data for up to week9 but that will increase with time.
So basically what it is I'm looking to display is the week number value as a column header.
Is this possible...although I'm tempted to just grab the data and display properly through code/(asp.net) but I was wondering if there was away to display it like this in SQL?
Does anyone know or think that that this might be the best way?
There are two ways of doing this with static SQL and dynamic SQL:
Static Pivot:
SELECT P.ItemNo, IsNull(P.[1], 0) as Wk1, IsNull(P.[2], 0) as Wk2
, IsNull(P.[3], 0) as Wk3, IsNull(P.[4], 0) as Wk4
, IsNull(P.[5], 0) as Wk5, IsNull(P.[6], 0) as Wk6
, IsNull(P.[7], 0) as Wk7, IsNull(P.[8], 0) as Wk8
, IsNull(P.[9], 0) as Wk9
FROM
(
SELECT ItemNo, WeekNo, [Value]
FROM dbo.Items
) I
PIVOT
(
SUM([Value])
FOR WeekNo IN ([1], [2], [3], [4], [5], [6], [7], [8], [9])
) as P
Dynamic Pivot:
DECLARE
#cols AS NVARCHAR(MAX),
#y AS INT,
#sql AS NVARCHAR(MAX);
-- Construct the column list for the IN clause
SET #cols = STUFF(
(SELECT N',' + QUOTENAME(w) AS [text()]
FROM (SELECT DISTINCT WeekNo AS W FROM dbo.Items) AS W
ORDER BY W
FOR XML PATH('')),
1, 1, N'');
-- Construct the full T-SQL statement
-- and execute dynamically
SET #sql = N'SELECT *
FROM (SELECT ItemNo, WeekNo, Value
FROM dbo.Items) AS I
PIVOT(SUM(Value) FOR WeekNo IN(' + #cols + N')) AS P;';
EXEC sp_executesql #sql;
GO
Maybe something like this:
Test data
CREATE TABLE #tbl
(
ITEMNO VARCHAR(100),
WEEKNO INT,
VALUE FLOAT
)
INSERT INTO #tbl
VALUES
('A1234',1,805),
('A2345',2,14.50),
('A3547',2,1396.70),
('A2208',1,17.65),
('A4326',6,19.99)
Week columns
DECLARE #cols VARCHAR(MAX)
;WITH Nbrs ( n ) AS (
SELECT 1 UNION ALL
SELECT 1 + n FROM Nbrs WHERE n < 52 )
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(n AS VARCHAR(2))),
QUOTENAME('WK'+CAST(n AS VARCHAR(2))))
FROM
Nbrs
Just the included weeks
DECLARE #cols VARCHAR(MAX)
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY WEEKNO ORDER BY WEEKNO) AS RowNbr,
WEEKNO
FROM
#tbl
)
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))),
QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))))
FROM
CTE
WHERE
CTE.RowNbr=1
Dynamic pivot
DECLARE #query NVARCHAR(4000)=
N'SELECT
*
FROM
(
SELECT
tbl.ITEMNO,
''WK''+CAST(tbl.WEEKNO AS VARCHAR(2)) AS WEEKNO,
tbl.VALUE
FROM
#tbl as tbl
) AS p
PIVOT
(
SUM(VALUE)
FOR WEEKNO IN ('+#cols+')
) AS pvt'
EXECUTE(#query)
Drop the temp table
DROP TABLE #tbl
Use Pivot, although quite a bit of code..
If you create report in reporting services, can use matrix..
Follow the below walkthrogh which explains it clearly
http://www.tsqltutorials.com/pivot.php
You can use PIVOT if you want to do this in sql directly.
It can be more efficient to use the SQL Server to do this as opposed to the client depending upon the size of the data and the aggregation.

Resources