SQL Server : dynamic pivot over 5 columns - sql

I'm having a very tough time trying to figure out how to do a dynamic pivot in SQL Server 2008 with multiple columns.
My sample table is as follows:
ID YEAR TYPE TOTAL VOLUME
DD1 2008 A 1000 10
DD1 2008 B 2000 20
DD1 2008 C 3000 30
DD1 2009 A 4000 40
DD1 2009 B 5000 50
DD1 2009 C 6000 60
DD2 2008 A 7000 70
DD2 2008 B 8000 80
DD2 2008 C 9000 90
DD2 2009 A 10000 100
DD2 2009 B 11000 110
DD2 2009 C 12000 120
and I'm trying the pivot it as follows:
ID 2008_A_TOTAL 2008_A_VOLUME 2008_B_TOTAL 2008_B_VOLUME 2008_C_TOTAL 2008_C_VOLUME 2009_A_TOTAL 2009_A_VOLUME 2009_B_TOTAL 2009_B_VOLUME 2009_C_TOTAL 2009_C_VOLUME
DD1 1000 10 2000 20 3000 30 4000 40 5000 50 6000 60
DD2 7000 70 8000 80 9000 90 10000 100 11000 110 12000 120
My SQL Server 2008 query is as follows to create the table:
CREATE TABLE ATM_TRANSACTIONS
(
ID varchar(5),
T_YEAR varchar(4),
T_TYPE varchar(3),
TOTAL int,
VOLUME int
);
INSERT INTO ATM_TRANSACTIONS
(ID,T_YEAR,T_TYPE,TOTAL,VOLUME)
VALUES
('DD1','2008','A',1000,10),
('DD1','2008','B',2000,20),
('DD1','2008','C',3000,30),
('DD1','2009','A',4000,40),
('DD1','2009','B',5000,50),
('DD1','2009','C',6000,60),
('DD2','2008','A',7000,70),
('DD2','2008','B',8000,80),
('DD2','2008','C',9000,90),
('DD2','2009','A',10000,100),
('DD2','2009','B',11000,110),
('DD2','2009','C',1200,120);
The T_Year column may change in the future but the T_TYPE column is generally know, so I'm not sure if I can use a combination of the PIVOT function in SQL Server with dynamic code?
I tried following the example here:
http://social.technet.microsoft.com/wiki/contents/articles/17510.t-sql-dynamic-pivot-on-multiple-columns.aspx
but I ended up with with weird results.

In order to get the result, you will need to look at unpivoting the data in the Total and Volume columns first before applying the PIVOT function to get the final result. My suggestion would be to first write a hard-coded version of the query then convert it to dynamic SQL.
The UNPIVOT process converts these multiple columns into rows. There are a few ways to UNPIVOT, you can use the UNPIVOT function or you can use CROSS APPLY. The code to unpivot the data will be similar to:
select id,
col = cast(t_year as varchar(4))+'_'+t_type+'_'+col,
value
from ATM_TRANSACTIONS t
cross apply
(
select 'total', total union all
select 'volume', volume
) c (col, value);
This gives you data in the format:
+-----+---------------+-------+
| id | col | value |
+-----+---------------+-------+
| DD1 | 2008_A_total | 1000 |
| DD1 | 2008_A_volume | 10 |
| DD1 | 2008_B_total | 2000 |
| DD1 | 2008_B_volume | 20 |
| DD1 | 2008_C_total | 3000 |
| DD1 | 2008_C_volume | 30 |
+-----+---------------+-------+
Then you can apply the PIVOT function:
select ID,
[2008_A_total], [2008_A_volume], [2008_B_total], [2008_B_volume],
[2008_C_total], [2008_C_volume], [2009_A_total], [2009_A_volume]
from
(
select id,
col = cast(t_year as varchar(4))+'_'+t_type+'_'+col,
value
from ATM_TRANSACTIONS t
cross apply
(
select 'total', total union all
select 'volume', volume
) c (col, value)
) d
pivot
(
max(value)
for col in ([2008_A_total], [2008_A_volume], [2008_B_total], [2008_B_volume],
[2008_C_total], [2008_C_volume], [2009_A_total], [2009_A_volume])
) piv;
Now that you have the correct logic, you can convert this to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(cast(t_year as varchar(4))+'_'+t_type+'_'+col)
from ATM_TRANSACTIONS t
cross apply
(
select 'total', 1 union all
select 'volume', 2
) c (col, so)
group by col, so, T_TYPE, T_YEAR
order by T_YEAR, T_TYPE, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id,' + #cols + '
from
(
select id,
col = cast(t_year as varchar(4))+''_''+t_type+''_''+col,
value
from ATM_TRANSACTIONS t
cross apply
(
select ''total'', total union all
select ''volume'', volume
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
This will give you a result:
+-----+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+
| id | 2008_A_total | 2008_A_volume | 2008_B_total | 2008_B_volume | 2008_C_total | 2008_C_volume | 2009_A_total | 2009_A_volume | 2009_B_total | 2009_B_volume | 2009_C_total | 2009_C_volume |
+-----+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+
| DD1 | 1000 | 10 | 2000 | 20 | 3000 | 30 | 4000 | 40 | 5000 | 50 | 6000 | 60 |
| DD2 | 7000 | 70 | 8000 | 80 | 9000 | 90 | 10000 | 100 | 11000 | 110 | 1200 | 120 |
+-----+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+--------------+---------------+

declare #stmt nvarchar(max)
select #stmt = isnull(#stmt + ', ', '') +
'sum(case when T_YEAR = ''' + T.T_YEAR + ''' and T_TYPE = ''' + T.T_TYPE + ''' then TOTAL else 0 end) as ' + quotename(T.T_YEAR + '_' + T.T_TYPE + '_TOTAL') + ',' +
'sum(case when T_YEAR = ''' + T.T_YEAR + ''' and T_TYPE = ''' + T.T_TYPE + ''' then VOLUME else 0 end) as ' + quotename(T.T_YEAR + '_' + T.T_TYPE + '_VOLUME')
from (select distinct T_YEAR, T_TYPE from ATM_TRANSACTIONS) as T
order by T_YEAR, T_TYPE
select #stmt = '
select
ID, ' + #stmt + ' from ATM_TRANSACTIONS group by ID'
exec sp_executesql
#stmt = #stmt
unfortunately, sqlfiddle.com is not working at the moment, so I cannot create an example for you.
The query created by dynamic SQL would be:
select
ID,
sum(case when T_YEAR = '2008' and T_TYPE = 'A' then TOTAL else 0 end) as 2008_A_TOTAL,
sum(case when T_YEAR = '2008' and T_TYPE = 'A' then VOLUME else 0 end) as 2008_A_VOLUME,
...
from ATM_TRANSACTIONS
group by ID

Please try:
DECLARE #pivv NVARCHAR(MAX),#Query NVARCHAR(MAX)
SELECT #pivv=COALESCE(#pivv+',','')+ QUOTENAME(T_YEAR+'_'+T_TYPE+'_TOTAL')+','+QUOTENAME(T_YEAR+'_'+T_TYPE+'_VOLUME') from ATM_TRANSACTIONS GROUP BY T_YEAR, T_TYPE
IF ISNULL(#pivv, '')<>''
SET #Query='SELECT * FROM(
SELECT ID, T_YEAR+''_''+T_TYPE+''_TOTAL'' TYP, TOTAL VAL from ATM_TRANSACTIONS UNION
SELECT ID, T_YEAR+''_''+T_TYPE+''_VOLUME'' TYP, VOLUME VAL from ATM_TRANSACTIONS
)x pivot (SUM(VAL) for TYP in ('+#pivv+')) as xx'
IF ISNULL(#Query, '')<>''
EXEC (#Query)

Related

Convert rows to column by date with pivot

I have read the stuff on MS pivot tables and I am still having problems getting this correct.
Data
wh_id | saledate | qty |
105 | 20190901 | 134.000000 |
105 | 20190902 | 190.000000 |
105 | 20190903 | 148.500000 |
105 | 20190904 | 157.500000 |
105 | 20190905 | 209.500000 |
I would like it to come out as a pivot table, like this:
wh_id | 1 | 2 | 3 | 4 | 5 |
105 | 134 | 190 |148.5 | 157.5 | 209.5 |
this the code :
DECLARE
#cols nvarchar(max)='' ,
#query nvarchar(max)=''
SET #cols = STUFF((SELECT ',' + QUOTENAME(DATEPART(dd, saledate))
FROM sales
WHERE month(saleDATE)=9 and year(saleDATE)=2019 and wh_id=105
GROUP BY saledate
ORDER BY saledate ASC
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'');
set #query = 'SELECT [wh_id], ' + #cols + '
from
(
select [wh_id], QUOTENAME(DATEPART(dd, saledate)) saledate,qty
from sales where month(saleDATE)=9 and year(saleDATE)=2019 and wh_id=105
) x
pivot
(
sum(qty)
for [saledate] in (' + #cols + ')
) p '
execute(#query);
but the result is like this
wh_id | 1 | 2 | 3 | 4 | 5 |
105 | 1 | 2 | 3 | 4 | 5 |
just change the source part from the above query remove QUOTENAME applied over saledate and try executing the query you will find the expected output.
set #query = 'SELECT [wh_id], ' + #cols + '
from
(
select [wh_id], DATEPART(dd, saledate) saledate,qty
from sales where month(saleDATE)=9 and year(saleDATE)=2019 and wh_id=105
) x
pivot
(
sum(qty)
for [saledate] in (' + #cols + ') ) p '
select wh_id,
max(case when rn = 1 then qty end) '1',
max(case when rn = 2 then qty end) '2',
max(case when rn = 3 then qty end) '3',
max(case when rn = 4 then qty end) '4',
max(case when rn = 5 then qty end) '5'
from
(
select wh_id,qty,
row_number() over(partition by wh_id order by qty) rn
from YourTableName
) src
group by wh_id
OutPut:-
Note:- Instead of using Pivot ...you can use this simple query...using ...case when and then with max aggregate function...
In above example....i'm forgot add decimal value in Table....

How to use pivot in two or more columns in SQL Server

This is my sample table
Item Stocks Sold Year
------------------------------------------------------
Shoes 30 5 2018
Slippers 15 15 2019
Sandals 20 10 2016
Pants 25 5 2018
Shoes 20 0 2017
What I'm trying to achieve is that the columns of Stocks and Sold will be posted by Year.
Example output :-
As of 2016 | As of 2017 | As of 2018 | As of 2019
----------------+------+--------+------+--------+------+---------+-------
Item Stocks | Sold | Stocks | Sold | Stocks | Sold | Stocks | Sold
----------------+------+--------+------+--------+------+---------+--------
Shoes | | 20 | 0 | 30 | 5 | |
Slippers | | | | | | 15 | 15
Sandals 20 | 10 | | | | | |
Pants | | | | 25 | 5 | |
Glad for any help :)
Try this:-
declare #Stock nvarchar(max)
(Select #Stock = Stuff( (select distinct ',[Stock_' + cast(year as varchar(5)) + ']' from Stock for xml path('')), 1,1,''))
declare #Sold nvarchar(max)
(Select #Sold = Stuff( (select distinct ',[Sold_' + cast(year as varchar(5)) + ']' from Stock for xml path('')), 1,1,''))
declare #col1 nvarchar(max)
(Select #col1 = Stuff( (select distinct ',cts.[Stock_' + cast(year as varchar(5)) + ']' + ',ctd.[Sold_' + cast(year as varchar(5)) + ']' from Stock for xml path('')), 1,1,''))
declare #query nvarchar(max) = '
; with cte as (
select Item, stock, ''Stock'' + ''_'' + Cast(year as varchar(10)) as colstock from stock)
, ct as (
select Item, Sold, ''Sold'' + ''_'' + Cast(year as varchar(10)) as colsold from stock)
, ctstock as (
select * from
( select item, stock, colstock from cte )
as d
pivot
( max(stock) for colstock in (' + #Stock + ') )
as p
)
, ctsold as (
select * from
( select item, sold, colsold from ct )
as d
pivot
( max(sold) for colsold in (' + #Sold + ') )
as p
)
select cts.item, ' + #col1 + ' from ctstock as cts inner join ctsold as ctd on cts.item = ctd.item'
exec sp_executesql #query
use conditional aggregate
SELECT Item,
SUM (CASE WHEN Year = 2016 THEN Stocks END) as Stock2016,
SUM (CASE WHEN Year = 2016 THEN Sold END) as Sold2016
FROM yourtable
GROUP BY Item

Columns to rows and rows to column conversion without pivot/unpivot

All I need is convert Column(A1/A2) into rows and rows(1 into Jan) into columns.
Input:
Here A1/A2 belongs to say A and they are calculated as A1/A2 for each month.
Month A1 A2 B1 B2 C1 C2
1 120 60 40 80 120 120
2 50 50 40 20 60 30
3 50 25 40 10 90 30
I need below o/p without using pivot and unpivot
O/P:
X Jan(1 is denoting Jan) Feb Mar
A 120/60(calculation:A1/A2) 40/80 120/120
B 50/50 40/20 60/30
C 50/25 40/10 90/30
I tried but my query is too long as I am using case and Union All three times each For A1 A2,B1 B2,C1 C2 etc.
Using cross apply(values ...) and conditional aggregation:
select
v.X
, Jan = max(case when t.month=1 then v.Value end)
, Feb = max(case when t.month=2 then v.Value end)
, Mar = max(case when t.month=3 then v.Value end)
from t
cross apply(values
('A',convert(varchar(3),A1)+'/'+convert(varchar(3),A2))
,('B',convert(varchar(3),B1)+'/'+convert(varchar(3),B2))
,('C',convert(varchar(3),C1)+'/'+convert(varchar(3),C2))
) v (X,Value)
group by v.X
rextester demo: http://rextester.com/XICI74484
returns:
+---+---------+-------+-------+
| X | Jan | Feb | Mar |
+---+---------+-------+-------+
| A | 120/60 | 50/50 | 50/25 |
| B | 40/80 | 40/20 | 40/10 |
| C | 120/120 | 60/30 | 90/30 |
+---+---------+-------+-------+
For the evaluation of the expressions:
select
v.X
, Jan = max(case when t.month=1 then v.Value end)
, Feb = max(case when t.month=2 then v.Value end)
, Mar = max(case when t.month=3 then v.Value end)
from t
cross apply(values
('A',(A1*1.0)/A2)
,('B',(B1*1.0)/B2)
,('C',(C1*1.0)/C2)
) v (X,Value)
group by v.X
returns:
+---+------+------+------+
| X | Jan | Feb | Mar |
+---+------+------+------+
| A | 2.00 | 1.00 | 2.00 |
| B | 0.50 | 2.00 | 4.00 |
| C | 1.00 | 2.00 | 3.00 |
+---+------+------+------+
You can use XML. This snippet may be more than you want but it shows how
--------------------------------------------------
-----------------Begin TRANSPOSE-----------------
--------------------------------------------------
DECLARE #xml XML
,#RowCount BIGINT
DECLARE #sSQl NVARCHAR(MAX)= 'SELECT (SELECT DISTINCT ColumnName FROM #TempTable WHERE CellId=Cell.CellId) as ColumnName,'
---------------------------------------------------
--Set up #table Here or outside the code... but #TABLE WILL BE DROPPED AT FINISH
---------------------------------------------------
--#############################################################################
-- the temp table #Table will contain what you want to Transpose
--#############################################################################
--SELECT * INTO #Table FROM Widgets W WHERE WidgetId IN (1096,803)
---------------------------------------------------
---------------- End #Table Setup ---------------
---------------------------------------------------
SET #xml = (SELECT *
,ROW_NUMBER() OVER (ORDER BY (SELECT 1
)) Rn
FROM #Table Row
FOR
XML AUTO
,ROOT('Root')
,ELEMENTS XSINIL
);
WITH RC AS
(SELECT COUNT(Row.value('.', 'nvarchar(MAX)')) [RowCount]
FROM #xml.nodes('Root/Row') AS WTable(Row))
,c AS(
SELECT b.value('local-name(.)','nvarchar(max)') ColumnName,
b.value('.[not(#xsi:nil = "true")]','nvarchar(max)') Value,
b.value('../Rn[1]','nvarchar(max)') Rn,
ROW_NUMBER() OVER (PARTITION BY b.value('../Rn[1]','nvarchar(max)') ORDER BY (SELECT 1)) Cell
FROM
#xml.nodes('//Root/Row/*[local-name(.)!="Rn"]') a(b)
),Cols AS (
SELECT DISTINCT c.ColumnName,
c.Cell
FROM c
)
INSERT INTO #TempTable (CellId,RowID,Value,ColumnName)
SELECT Cell,Rn,Value,REPLACE(c.ColumnName,'_x0023_','#')
FROM c
SELECT #sSQL = #sSQl + '(SELECT T2.Value FROM #Temptable T2 WHERE T2.CellId=Cell.CellID AND T2.Rowid=' + CAST(T.RowId AS NVARCHAR) + ') AS __________________Value____________________'
+ CAST(T.RowID AS NVARCHAR) + ','
FROM (SELECT DISTINCT
RowId
FROM #TempTable
) T
SET #sSQl = LEFT(#sSQL, LEN(#sSQL) - 1) + ' FROM (SELECT DISTINCT CellId FROM #TempTable) Cell order by columnname'
EXECUTE sp_Executesql #sSQl
--here you will have your output
-- PRINT #sSQl
DROP TABLE #Table
DROP TABLE #TempTable
--------------------------------------------------
-----------------END TRANSPOSE-------------------
--------------------------------------------------

extend current query, calculated columns

My table looks for example like this:
Name date result
A 2012-01-01 1
A 2012-02-01 2
B 2013-01-01 1
...
For a full example: http://sqlfiddle.com/#!3/0226b/1
At the moment I have a working query that counts the rows by person and year: http://sqlfiddle.com/#!3/0226b/3
This is perfect, but what I want is some extra information for 2014. i need to count how many rows I have for every result.
something like this:
NAME 1 2 3 2014 2013 2012 TOTAL
Person B 4 0 2 6 2 2 10
Person A 2 1 1 4 3 4 11
Person C 1 1 1 3 1 0 4
Even better would be that I give the result-columns a good name (1 = lost, 2= draw, 3=won):
NAME lost draw won 2014 2013 2012 TOTAL
Person B 4 0 2 6 2 2 10
Person A 2 1 1 4 3 4 11
Person C 1 1 1 3 1 0 4
I tried to add some extra code, like:
select #colsResult
= STUFF((SELECT ',' + QUOTENAME(result)
from list
group by result
order by result
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
I have as result:
,[1]
,[2]
,[3]
But if I run the whole code I get an error, invallid column name...
Since you have two columns that you now want to PIVOT, you'll first have to unpivot those columns and then convert those values into the new columns.
Starting in SQL Server 2005, you could use CROSS APPLY to unpivot the columns. The basic syntax will be similar to:
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', result
) c (old_col_name, new_col)
See SQL Fiddle with Demo. This query gets you a list of names, with the "new columns" and then the Total entries for each name.
| NAME | NEW_COL | TOTAL |
|----------|---------|-------|
| Person A | 2012 | 11 |
| Person A | 1 | 11 |
| Person A | 2012 | 11 |
| Person A | 2 | 11 |
You'll see that the dates and the results are now both stored in "new_col". These values will now be used as the new column names. If you have a limited number of columns, then you would simply hard-code the query:
select name, lost = [1],
draw=[2], won = [3],
[2014], [2013], [2012], Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', result
) c (old_col_name, new_col)
) src
pivot
(
count(new_col)
for new_col in([1], [2], [3], [2014], [2013], [2012])
) piv
order by [2014];
See SQL Fiddle with Demo
Now since your years are dynamic, then you'll need to use dynamic sql. But it appears that you have 3 results and potentially multiple years - so I'd use a combination of static/dynamic sql to make this easier:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#orderby nvarchar(max)
select #cols
= STUFF((SELECT ',' + QUOTENAME(year(date))
from list
group by year(date)
order by year(date) desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #orderby = 'ORDER BY ['+cast(year(getdate()) as varchar(4)) + '] desc'
set #query = 'SELECT name, lost = [1],
draw=[2], won = [3],' + #cols + ', Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select ''dt'', dt union all
select ''result'', result
) c (old_col_name, new_col)
) x
pivot
(
count(new_col)
for new_col in ([1], [2], [3],' + #cols + ')
) p '+ #orderby
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives a result:
| NAME | LOST | DRAW | WON | 2014 | 2013 | 2012 | TOTAL |
|----------|------|------|-----|------|------|------|-------|
| Person B | 7 | 1 | 2 | 6 | 2 | 2 | 10 |
| Person A | 5 | 3 | 3 | 4 | 3 | 4 | 11 |
| Person C | 2 | 1 | 1 | 3 | 1 | 0 | 4 |
If you want to only filter the result columns for the current year, then you can perform this filtering a variety of ways but the easiest you be to include a filter in the unpivot. The hard-coded version would be:
select name, lost = [1],
draw=[2], won = [3],
[2014], [2013], [2012], Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', case when dt = 2014 then result end
) c (old_col_name, new_col)
) src
pivot
(
count(new_col)
for new_col in([1], [2], [3], [2014], [2013], [2012])
) piv
order by [2014] desc;
See SQL Fiddle with Demo. Then the dynamic sql version would be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#orderby nvarchar(max),
#currentYear varchar(4)
select #currentYear = cast(year(getdate()) as varchar(4))
select #cols
= STUFF((SELECT ',' + QUOTENAME(year(date))
from list
group by year(date)
order by year(date) desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #orderby = 'ORDER BY ['+ #currentYear + '] desc'
set #query = 'SELECT name, lost = [1],
draw=[2], won = [3],' + #cols + ', Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select ''dt'', dt union all
select ''result'', case when dt = '+#currentYear+' then result end
) c (old_col_name, new_col)
) x
pivot
(
count(new_col)
for new_col in ([1], [2], [3],' + #cols + ')
) p '+ #orderby
exec sp_executesql #query;
See SQL Fiddle with Demo. This version will give a result:
| NAME | LOST | DRAW | WON | 2014 | 2013 | 2012 | TOTAL |
|----------|------|------|-----|------|------|------|-------|
| Person B | 4 | 0 | 2 | 6 | 2 | 2 | 10 |
| Person A | 2 | 1 | 1 | 4 | 3 | 4 | 11 |
| Person C | 1 | 1 | 1 | 3 | 1 | 0 | 4 |

Sql pivot - but dont sum the values

I have a table schema that looks like this
CREATE TABLE [dbo].[Discounts](
[Id] [int] NOT NULL,
[ProductId] [varchar(50)] NOT NULL,
[LowerBoundDays] [int] NOT NULL,
[UpperBoundDays] [int] NOT NULL,
[Discount] [decimal](18, 4) NOT NULL,
And some data like this
lower upper discount(%)
product1 0 10 0
product1 10 30 1
product1 30 60 2
product1 60 90 3
product1 90 120 4
product2 0 10 0
product2 10 30 1
product2 30 60 2
product2 60 90 3
product2 90 120 4
How can I do a pivot query to get 2 rows that look like this:
0-10 10-30 30-60 60-90 90-120
product1 0 1 2 3 4
product2 0 1 2 3 4
Since you are using SQL Server, there are several ways that you can convert the rows of data into columns.
You can use an aggregate function with a CASE expression to get the result:
select productid,
max(case when lower = 0 and upper = 10 then discount end) [0-10],
max(case when lower = 10 and upper = 30 then discount end) [10-30],
max(case when lower = 30 and upper = 60 then discount end) [30-60],
max(case when lower = 60 and upper = 90 then discount end) [60-90],
max(case when lower = 90 and upper = 120 then discount end) [90-120]
from CorporateSpread
group by productid;
See SQL Fiddle with Demo.
If you are using SQL Server 2005+, then you can use the PIVOT function:
select productid, [0-10], [10-30], [30-60], [60-90],[90-120]
from
(
select productid,
discount,
cast(lower as varchar(10)) + '-' + cast(upper as varchar(10)) rng
from CorporateSpread
) d
pivot
(
max(discount)
for rng in ([0-10], [10-30], [30-60], [60-90],[90-120])
) piv;
See SQL Fiddle with Demo.
The above two version work great if you have a known number of values, but if you have an unknown number of ranges, then you will need to to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(cast(lower as varchar(10)) + '-' + cast(upper as varchar(10)))
from CorporateSpread
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT productid, ' + #cols + '
from
(
select productid,
discount,
cast(lower as varchar(10)) + ''-'' + cast(upper as varchar(10)) rng
from CorporateSpread
) x
pivot
(
max(discount)
for rng in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. All versions will give a result:
| PRODUCTID | 0-10 | 10-30 | 30-60 | 60-90 | 90-120 |
-----------------------------------------------------
| product1 | 0 | 1 | 2 | 3 | 4 |
| product2 | 0 | 1 | 2 | 3 | 4 |