How can I cross tab this table [duplicate] - sql

This question already has answers here:
Simple way to transpose columns and rows in SQL?
(9 answers)
Closed 9 years ago.
I am using sql server 2008 and I want to cross tab this table
Month Affec KPI Total KPI_% Out rep_in_10 ftm
Jan-11 30565 34623 42003 82.4 7380 7003 5024
Jan-12 20955 25915 27857 93 1942 4754 3518
Feb-11 27754 27757 36483 76.1 8726 5648 4189
Feb-12 19513 25188 26962 93.4 1774 5768 4185
Mar-11 22838 23758 29951 79.3 6193 4394 3282
Mar-12 18778 25098 26177 95.9 1079 5784 4105
Apr-11 20235 21950 25917 84.7 3967 3895 2967
to
Jan-11 Jan-12 Feb-11 Feb-12 Mar-11 Apr-11
Affec 30565
KPI 34623
Total 42003
KPI_% 82.4
Out 7380
rep_in_10 7003

In my opinion you shouldn't do this. You can achieve this easily on presentation layer with PHP or whatever you are using. Databases are there to get you the data, not to format it nicely. See Mahmoud's answer more as a proof of concept. But the query will never be as fast as the query you have right now to get the data. Maintenance may be another argument against it.

For this type of data transformation, you will need to use the UNPIVOT function and then apply the PIVOT function in SQL Server.
There are two ways to perform this, either hard-coding the values with a static version of using dynamic sql to generate the values as run-time.
Static Version:
The UNPIVOT piece of this takes the data from your multiple columns and transforms it into two rows. Note, the one thing to keep in mind with unpivot is that the datatypes must be the same. So you might have to perform a data type conversion on the data.:
select [Month], value, col
from
(
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[Affec], [KPI], [Total], [KPI_%], [Out], [rep_in_10], [ftm]
from yourtable
) src
unpivot
(
value
for col in ([Affec], [KPI], [Total], [KPI_%], [Out], [rep_in_10], [ftm])
) unpiv
See SQL Fiddle with Demo
Result:
| MONTH | VALUE | COL |
-------------------------------------
| January-2011 | 30565 | Affec |
| January-2011 | 34623 | KPI |
| January-2011 | 42003 | Total |
| January-2011 | 82.4 | KPI_% |
| January-2011 | 7380 | Out | ---etc
Then you apply the PIVOT to the months:
select *
from
(
select [Month], value, col
from
(
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[Affec], [KPI], [Total], [KPI_%], [Out], [rep_in_10], [ftm]
from yourtable
) src
unpivot
(
value
for col in ([Affec], [KPI], [Total], [KPI_%], [Out], [rep_in_10], [ftm])
) unpiv
) src
pivot
(
max(value)
for month in ([January-2011], [February-2011], [March-2011],
[April-2011], [January-2012], [February-2012], [March-2012])
) piv
See SQL Fiddle with Demo
Result:
| COL | JANUARY-2011 | FEBRUARY-2011 | MARCH-2011 | APRIL-2011 | JANUARY-2012 | FEBRUARY-2012 | MARCH-2012 |
------------------------------------------------------------------------------------------------------------------
| Affec | 30565 | 27754 | 22838 | 20235 | 20955 | 19513 | 18778 |
| ftm | 5024 | 4189 | 3282 | 2967 | 3518 | 4185 | 4105 |
| KPI | 34623 | 27757 | 23758 | 21950 | 25915 | 25188 | 25098 |
| KPI_% | 82.4 | 76.1 | 79.3 | 84.7 | 93 | 93.4 | 95.9 |
| Out | 7380 | 8726 | 6193 | 3967 | 1942 | 1774 | 1079 |
| rep_in_10 | 7003 | 5648 | 4394 | 3895 | 4754 | 5768 | 5784 |
| Total | 42003 | 36483 | 29951 | 25917 | 27857 | 26962 | 26177 |
Dynamic Version:
The above works great, if you have a known number of values but it your values are unknown then you will want to use dynamic sql. I am going to guess that you will want a dynamic version since you will have a unknown number of dates:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsUnpivot = STUFF((SELECT DISTINCT ','
+ quotename(c.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('Month')
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols = STUFF((SELECT ',' + QUOTENAME(DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)))
from yourtable
group by [Month]
order by [Month]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT col,' + #cols + ' from
(
select [Month], value, col
from
(
select DateName(month,[Month]) +''-''+Cast(datepart(year, [month]) as varchar(4)) Month,
[Affec], [KPI], [Total], [KPI_%], [Out], [rep_in_10], [ftm]
from yourtable
) src
unpivot
(
value
for col in ('+#colsunpivot+')
) unpiv
) x
pivot
(
max(value)
for [Month] in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result will be the same as the dynamic version.
UNION ALL/CASE with aggregate:
Lastly, if you do not have access to either the UNPIVOT or PIVOT functions, then you can use a UNION ALL to unpivot and an aggregate function with a CASE to pivot the data:
select col,
max(case when month='January-2011' then value end) [January-2011],
max(case when month='February-2011' then value end) [February-2011],
max(case when month='March-2011' then value end) [March-2011],
max(case when month='April-2011' then value end) [April-2011],
max(case when month='January-2012' then value end) [January-2012],
max(case when month='February-2012' then value end) [February-2012],
max(case when month='March-2012' then value end) [March-2012]
from
(
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[Affec] value,
'Affec' col
from yourtable
union all
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[KPI] value,
'KPI' col
from yourtable
union all
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[Total] value,
'Total' col
from yourtable
union all
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[KPI_%] value,
'KPI_%' col
from yourtable
union all
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[rep_in_10] value,
'rep_in_10' col
from yourtable
union all
select DateName(month,[Month]) +'-'+Cast(datepart(year, [month]) as varchar(4)) Month,
[ftm] value,
'ftm' col
from yourtable
) src
group by col
See SQL Fiddle with Demo

Like this:
;WITH UNpivoted
AS
(
SELECT MonthsValue, Value, month
FROM (SELECT [Month],
CAST([Affec] AS VARCHAR(10)) Affec ,
CAST([KPIprecent] AS VARCHAR(10)) KPIprecent,
CAST([Total] AS VARCHAR(10)) Total,
CAST([KPI] AS VARCHAR(10)) KPI,
CAST([Out] AS VARCHAR(10)) [Out],
CAST([rep_in_10] AS VARCHAR(10)) [rep_in_10],
CAST([ftm] AS VARCHAR(10)) ftm
FROM table1
) t
UNPIVOT
(
MonthsValue FOR Value IN([Affec],
[KPIprecent],
[Total],
[KPI],
[Out],
[rep_in_10],
[ftm])
) u
)
SELECT
value,
[Jan-11],
[Jan-12],
[Feb-11],
[Feb-12],
[Mar-11],
[Mar-12],
[Apr-11]
FROM
(
SELECT monthsvalue, value, month
FROM Unpivoted
) t
PIVOT
(MAX(monthsvalue) for Month IN ([Jan-11],
[Jan-12],
[Feb-11],
[Feb-12],
[Mar-11],
[Mar-12],
[Apr-11])
) p;
SQL Fiddle Demo
This will give you:
| VALUE | JAN-11 | JAN-12 | FEB-11 | FEB-12 | MAR-11 | MAR-12 | APR-11 |
-----------------------------------------------------------------------------
| Affec | 30565 | 20955 | 27754 | 19513 | 22838 | 18778 | 20235 |
| ftm | 5024 | 3518 | 4189 | 4185 | 3282 | 4105 | 2967 |
| KPI | 82.4 | 93.0 | 76.1 | 93.4 | 79.3 | 95.9 | 84.7 |
| KPIprecent | 34623 | 25915 | 27757 | 25188 | 23758 | 25098 | 21950 |
| Out | 7380 | 1942 | 8726 | 1774 | 6193 | 1079 | 3967 |
| rep_in_10 | 7003 | 4754 | 5648 | 5768 | 4394 | 5784 | 3895 |
| Total | 42003 | 27857 | 36483 | 26962 | 29951 | 26177 | 25917 |

Related

SQL combine 2 table and pivot

I don't understand how PIVOT works in SQL. I have 2 tables and I would like to pivot 1 of them in order to get only 1 table with all the data together. I've attached an image with the tables I have and the result that I would like to get.
CREATE TABLE TABLE1
([serie_id] varchar(4), [Maturity] int, [Strategy] int, [Lifetime] varchar(4), [L_max] decimal(10, 5), [W_max] decimal(10, 5), [H_max] decimal(10, 5))
;
INSERT INTO TABLE1
([serie_id], [Maturity], [Strategy], [Lifetime], [L_max], [W_max], [H_max])
VALUES
('id_1', 3, 1, '2', 2.200, 1.400, 1.400),
('id_2', 3, 1, '2', 3.400, 1.800, 2.100),
('id_3', 3, 1, NULL, 24.500, 14.500, 15.000),
('id_4', 3, 1, NULL, 28.000, 24.500, 14.000)
;
CREATE TABLE TABLE2
([serie_id] varchar(4), [L_value] decimal(10, 5), [lrms] decimal(10, 5), [latTmax] decimal(10, 5), [Rdc] decimal(10, 5))
;
INSERT INTO TABLE2
([serie_id], [L_value], [lrms], [latTmax], [Rdc])
VALUES
('id_1', 67.000, 400.000, 400.000, 0.250),
('id_1', 90.000, 330.000, 330.000, 0.350),
('id_1', 120.000, 370.000, 370.000, 0.300),
('id_1', 180.000, 330.000, 300.000, 0.350),
('id_2', 260.000, 300.000, 300.000, 0.400),
('id_2', 360.000, 280.000, 280.000, 0.450),
('id_3', 90.000, 370.000, 370.000, 0.300),
('id_4', 160.000, 340.000, 340.000, 0.400)
;
SQLFiddle
If someone could help me with the SQL query I would appreciate it so much.
In order to get your final result, you are going to have to implement a variety of methods including unpivot, pivot, along with the use of a windowing function like row_number().
Since you have multiple columns in Table2 that need to be pivoted, then you will need to unpivot them first. This is the reverse of pivot, which converts your multiple columns into multiple rows. But before you unpivot, you need some value to identify the values of each row using row_number() - sounds complicated, right?
First, query table2 using the windowing function row_number(). This creates a unique identifier for each row and allows you to easily be able to associate the values for id_1 from any of the others.
select serie_id, l_value, lrms, latTmax, Rdc,
rn = cast(row_number() over(partition by serie_id order by serie_id)
as varchar(10))
from table2;
See Demo. Once you've created this unique identifier, then you will unpivot the L_value, lrms, latTmax, and rdc. You can unpivot the data using several different methods, including the unpivot function, CROSS APPLY, or UNION ALL.
select serie_id,
col, value
from
(
select serie_id, l_value, lrms, latTmax, Rdc,
rn = cast(row_number() over(partition by serie_id order by serie_id)
as varchar(10))
from table2
) d
cross apply
(
select 'L_value_'+rn, L_value union all
select 'lrms_'+rn, lrms union all
select 'latTmax_'+rn, latTmax union all
select 'Rdc_'+rn, Rdc
) c (col, value)
See SQL Fiddle with Demo. The data from table2 is not in a completely different format that can be pivoted into the new columns:
| SERIE_ID | COL | VALUE |
|----------|-----------|-------|
| id_1 | L_value_1 | 67 |
| id_1 | lrms_1 | 400 |
| id_1 | latTmax_1 | 400 |
| id_1 | Rdc_1 | 0.25 |
| id_1 | L_value_2 | 90 |
| id_1 | lrms_2 | 330 |
| id_1 | latTmax_2 | 330 |
| id_1 | Rdc_2 | 0.35 |
The final step would be to PIVOT the data above into the final result:
select serie_id, maturity, strategy, lifetime, l_max, w_max, h_max,
L_value_1, lrms_1, latTmax_1, Rdc_1,
L_value_2, lrms_2, latTmax_2, Rdc_2,
L_value_3, lrms_3, latTmax_3, Rdc_3,
L_value_4, lrms_4, latTmax_4, Rdc_4
from
(
select t1.serie_id, t1.maturity, t1.strategy, t1.lifetime,
t1.l_max, t1.w_max, t1.h_max,
t2.col, t2.value
from table1 t1
inner join
(
select serie_id,
col, value
from
(
select serie_id, l_value, lrms, latTmax, Rdc,
rn = cast(row_number() over(partition by serie_id order by serie_id)
as varchar(10))
from table2
) d
cross apply
(
select 'L_value_'+rn, L_value union all
select 'lrms_'+rn, lrms union all
select 'latTmax_'+rn, latTmax union all
select 'Rdc_'+rn, Rdc
) c (col, value)
) t2
on t1.serie_id = t2.serie_id
) d
pivot
(
max(value)
for col in (L_value_1, lrms_1, latTmax_1, Rdc_1,
L_value_2, lrms_2, latTmax_2, Rdc_2,
L_value_3, lrms_3, latTmax_3, Rdc_3,
L_value_4, lrms_4, latTmax_4, Rdc_4)
) p;
See SQL Fiddle with Demo.
If you have an unknown number of values in Table2 then you will need to use dynamic SQL to create a sql string that will be executed. Converting the above code to dynamic sql is pretty easy once you have the logic correct. The code will be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols
= STUFF((SELECT ',' + QUOTENAME(col+cast(rn as varchar(10)))
from
(
select rn = cast(row_number() over(partition by serie_id order by serie_id)
as varchar(10))
from table2
) d
cross apply
(
select 'L_value_', 0 union all
select 'lrms_', 1 union all
select 'latTmax_', 2 union all
select 'Rdc_', 3
) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT serie_id, maturity, strategy, lifetime, l_max,
w_max, h_max,' + #cols + N'
from
(
select t1.serie_id, t1.maturity, t1.strategy, t1.lifetime,
t1.l_max, t1.w_max, t1.h_max,
t2.col, t2.value
from table1 t1
inner join
(
select serie_id,
col, value
from
(
select serie_id, l_value, lrms, latTmax, Rdc,
rn = cast(row_number() over(partition by serie_id order by serie_id)
as varchar(10))
from table2
) d
cross apply
(
select ''L_value_''+rn, L_value union all
select ''lrms_''+rn, lrms union all
select ''latTmax_''+rn, latTmax union all
select ''Rdc_''+rn, Rdc
) c (col, value)
) t2
on t1.serie_id = t2.serie_id
) x
pivot
(
max(value)
for col in (' + #cols + N')
) p '
exec sp_executesql #query
See SQL Fiddle with Demo
Both versions will give a result of:
| SERIE_ID | MATURITY | STRATEGY | LIFETIME | L_MAX | W_MAX | H_MAX | L_VALUE_1 | LRMS_1 | LATTMAX_1 | RDC_1 | L_VALUE_2 | LRMS_2 | LATTMAX_2 | RDC_2 | L_VALUE_3 | LRMS_3 | LATTMAX_3 | RDC_3 | L_VALUE_4 | LRMS_4 | LATTMAX_4 | RDC_4 |
|----------|----------|----------|----------|-------|-------|-------|-----------|--------|-----------|-------|-----------|--------|-----------|--------|-----------|--------|-----------|--------|-----------|--------|-----------|--------|
| id_1 | 3 | 1 | 2 | 2.2 | 1.4 | 1.4 | 67 | 400 | 400 | 0.25 | 90 | 330 | 330 | 0.35 | 120 | 370 | 370 | 0.3 | 180 | 330 | 300 | 0.35 |
| id_2 | 3 | 1 | 2 | 3.4 | 1.8 | 2.1 | 260 | 300 | 300 | 0.4 | 360 | 280 | 280 | 0.45 | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) |
| id_3 | 3 | 1 | (null) | 24.5 | 14.5 | 15 | 90 | 370 | 370 | 0.3 | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) |
| id_4 | 3 | 1 | (null) | 28 | 24.5 | 14 | 160 | 340 | 340 | 0.4 | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) |

SQLServer 2008 PIVOT on multiple fields issue

I am trying to using PIVOT on 3 fields/Columns i.e Site, Value, Date.I am using SQLServer 2008.Please check below my actual table and desired output table and the pivot query i used
Actual Table:
**tblReference**
**ID** **Refer_code** **Site** **Value** **Date**
1 9290 CA 12.5 2014-01-01 20:20:41
5 9290 TX 12.6 2014-01-05 18:20:30
2 6651 CA 13.5 2014-01-01 21:20:21
3 7442 TX 14.5 2014-01-05 19:15:14
4 8093 CA 15.5 2014-01-01 19:20:20
6 8093 TX 16.5 2014-01-05 20:20:20
**Desired output table:**
**Refer_code** **Site_1** **Site_2** **Val_1** **Val_2** **StartDate** **EndDate**
9290 CA TX 12.5 12.6 2014-01-01 2014-01-05
20:20:41 18:20:30
6651 CA NULL 13.5 NULL 2014-01-01 NULL
21:20:21
7442 NULL TX NULL 14.5 NULL 2014-01-05
19:15:14
8093 CA TX 15.5 16.5 2014-01-01 2014-01-05
19:20:20 20:20:20
**Query:**
SELECT Refer_code, [Site_1], [Site_2], [Date_1] AS StartDate, [Date_2] AS EndDate, [Val_1], [Val_2]
FROM
(
SELECT Refer_code, Site, 'Site_'+ cast(row_number() over(partition by Refer_code order by Date) as nvarchar(50)) SiteVal,Date,'Date_'+cast(row_number() over(partition by Refer_code order by Date) as nvarchar(50)) DateVal,Value,'Val_'+cast(row_number() over(partition by Refer_code order by Date) as nvarchar(50)) Val
FROM tblReference
) x
pivot
(
min(Site)
for SiteVal in ([Site_1], [Site_2])
) p
pivot
(
min(Date)
for DateVal in ([Date_1], [Date_2])
) s
pivot
(
min(Value)
for Val in ([Val_1], [Val_2])
) t
The above query is not returning the result as expected.Please help me on the solution.
There are several ways to get the desired result. The easiest way might be to use an aggregate function with a CASE expression but you can also use the PIVOT function.
Aggregate with CASE:
I would take the following steps, first create a unique sequence for each refer_code using row_number():
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference;
See SQL Fiddle with Demo.
Once you have the unique sequence you can easily convert your existing data into multiple columns by applying an aggregate function with a CASE expression:
;with cte as
(
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference
)
select refer_code,
max(case when seq = 1 then site end) site1,
max(case when seq = 2 then site end) site2,
max(case when seq = 1 then value end) value1,
max(case when seq = 2 then value end) value2,
max(case when seq = 1 then date end) startdate,
max(case when seq = 2 then date end) enddate
from cte
group by refer_code;
See SQL Fiddle with Demo
PIVOT:
If you want to use the PIVOT function and you need to pivot multiple columns I would suggest looking at unpivoting those columns first, then applying the pivot function. You will need to create the unique sequence which is used for your new column names, then you need to unpivot the site, date and value columns. Since you are using SQL Server 2008+ you can use CROSS APPLY with VALUEs to unpivot the multiple columns into multiple rows:
;with cte as
(
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference
)
select *
from
(
select refer_code,
col = col + cast(seq as varchar(10)),
val
from cte
cross apply
(
values
('Site', site),
('Value', cast(value as varchar(10))),
('Date', convert(varchar(10), date, 120))
) c (col, val)
) d;
See SQL Fiddle with Demo. This gets your data into the format of:
| REFER_CODE | COL | VAL |
|------------|--------|------------|
| 6651 | Site1 | CA |
| 6651 | Value1 | 13.50 |
| 6651 | Date1 | 2014-01-01 |
| 7442 | Site1 | TX |
| 7442 | Value1 | 14.50 |
| 7442 | Date1 | 2014-01-05 |
Now you can easily apply the pivot function to get the final result, so the entire code will be:
;with cte as
(
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference
)
select refer_code,
site1, site2,
startdate = date1, enddate = date2,
value1, value2
from
(
select refer_code,
col = col + cast(seq as varchar(10)),
val
from cte
cross apply
(
values
('Site', site),
('Value', cast(value as varchar(10))),
('Date', convert(varchar(10), date, 120))
) c (col, val)
) d
pivot
(
max(val)
for col in (site1, site2, date1, date2,
value1, value2)
) piv;
See SQL Fiddle with Demo. Both will give a result of:
| REFER_CODE | SITE1 | SITE2 | STARTDATE | ENDDATE | VALUE1 | VALUE2 |
|------------|-------|--------|------------|------------|--------|--------|
| 6651 | CA | (null) | 2014-01-01 | (null) | 13.50 | (null) |
| 7442 | TX | (null) | 2014-01-05 | (null) | 14.50 | (null) |
| 8093 | CA | TX | 2014-01-01 | 2014-01-05 | 15.50 | 16.50 |
| 9290 | CA | TX | 2014-01-01 | 2014-01-05 | 12.50 | 12.60 |
Edit:
Based on your comment that you need the columns to be CA as site1, etc. then the easiest way to get the result would be using an aggregate function with CASe:
select refer_code,
max(case when site = 'CA' then site end) site1,
max(case when site = 'TX' then site end) site2,
max(case when site = 'CA' then value end) value1,
max(case when site = 'TX' then value end) value2,
max(case when site = 'CA' then date end) startdate,
max(case when site = 'TX' then date end) endate
from tblReference
group by refer_code;
See SQL Fiddle with Demo. This gives a result:
| REFER_CODE | SITE1 | SITE2 | VALUE1 | VALUE2 | STARTDATE | ENDATE |
|------------|--------|--------|--------|--------|--------------------------------|--------------------------------|
| 6651 | CA | (null) | 13.5 | (null) | January, 01 2014 21:20:21+0000 | (null) |
| 7442 | (null) | TX | (null) | 14.5 | (null) | January, 05 2014 19:15:14+0000 |
| 8093 | CA | TX | 15.5 | 16.5 | January, 01 2014 19:20:20+0000 | January, 05 2014 20:20:20+0000 |
| 9290 | CA | TX | 12.5 | 12.6 | January, 01 2014 20:20:41+0000 | January, 05 2014 18:20:30+0000 |

SQL separate columns by rows value (pivot)

Table1
| MODULE | COUNT | YEAR |
-------------------------
| M1 | 12 | 2011 |
| M1 | 43 | 2012 |
| M2 | 5 | 2011 |
| M3 | 24 | 2011 |
| M4 | 22 | 2011 |
| M4 | 11 | 2012 |
| M5 | 10 | 2012 |
I want to display like this
| MODULE | 2011 | 2012 |
----------------------------
| M1 | 12 | 43 |
| M2 | 5 | - |
| M3 | 24 | - |
| M4 | 22 | 11 |
| M5 | - | 10 |
This can be done using PIVOT query. Or the following:
select Module,
SUM(CASE WHEN Year='2011' then Count ELSE 0 END) as [2011],
SUM(CASE WHEN Year='2012' then Count ELSE 0 END) as [2012]
FROM T
GROUP BY Module
SQL Fiddle demo
You can use PIVOT for that:
SELECT
Module,
[2011], [2012]
FROM
(
SELECT
*
FROM Table1
) AS SourceTable
PIVOT
(
SUM([Count])
FOR [Year] IN ([2011], [2012])
) AS PivotTable;
You can also use this dynamic query if you don't have limited Year
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT distinct ',' + QUOTENAME([Year])
from Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT Module,' + #cols + '
FROM
(
Select *
FROM Table1
) dta
PIVOT
(
SUM([Count])
FOR [Year] IN (' + #cols + ')
) pvt '
EXECUTE(#query);
Result:
| MODULE | 2011 | 2012 |
----------------------------
| M1 | 12 | 43 |
| M2 | 5 | (null) |
| M3 | 24 | (null) |
| M4 | 22 | 11 |
| M5 | (null) | 10 |
See this SQLFiddle
Update
You can also use this alternative dynamic method: (Dynamic of the query given by #valex)
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT distinct ','
+ ' SUM(CASE WHEN YEAR= ''' + CAST(Year AS varchar(50))
+ ''' THEN [COUNT] ELSE ''-'' END) AS ' + QUOTENAME([Year])
from Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT Module, ' + #cols + '
FROM Table1 GROUP BY Module'
EXECUTE(#query);
See this SQLFiddle
SELECT *
FROM Tablename
PIVOT (
AVG([Count] FOR [Year] IN (2011, 2012, 2013)) AS AvgCount
)
I think you need to look into the relational operator PIVOT.
Using PIVOT and UNPIVOT
SELECT Module,
[2011], [2012]
FROM T
PIVOT (SUM(Count) FOR Year IN ([2011], [2012])) AS PivotTable;

grouping and switching the columns and rows

I don't know if this would officially be called a pivot, but the result that I would like is this:
+------+---------+------+
| Alex | Charley | Liza |
+------+---------+------+
| 213 | 345 | 1 |
| 23 | 111 | 5 |
| 42 | 52 | 2 |
| 323 | | 5 |
| 23 | | 1 |
| 324 | | 5 |
+------+---------+------+
my input data is in this form:
+-----+---------+
| Apt | Name |
+-----+---------+
| 213 | Alex |
| 23 | Alex |
| 42 | Alex |
| 323 | Alex |
| 23 | Alex |
| 324 | Alex |
| 345 | Charley |
| 111 | Charley |
| 52 | Charley |
| 1 | Liza |
| 5 | Liza |
| 2 | Liza |
| 5 | Liza |
| 1 | Liza |
| 5 | Liza |
+-----+---------+
because I have approximately 100 names, I don't want to have to do a ton of sub queries lik this
select null, null, thirdcolumn from...
select null, seconcolumn from...
select firstcolumn from...
Is there a way to do this with PIVOT or otherwise?
You can do this with dynamic PIVOT and the ROW_NUMBER() function:
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT DISTINCT *
FROM #test)
,cte2 AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Apt)RowRank
FROM cte)
SELECT *
FROM cte2
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Distinct List, Specific Order
Edit: If you don't want the list to be distinct, eliminate the first cte above, and if you want to keep arbitrary ordering change the ORDER BY to (SELECT 1):
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT *
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Full List, Arbitrary Order
And finally, if you didn't want the RowRank field in your results, just re-use the #cols variable in your SELECT:
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT '+#cols+'
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
Oh, this is something of a pain, but you can do it with SQL. You are trying to concatenate the columns.
select seqnum,
max(case when name = 'Alex' then apt end) as Alex,
max(case when name = 'Charley' then apt end) as Charley,
max(case when name = 'Liza' then apt end) as Liza
from (select t.*, row_number() over (partition by name order by (select NULL)) as seqnum
from t
) t
group by seqnum
order by seqnum;
As a note: there is no guarantee that the original ordering will be the same within each column. As you know, SQL tables are inherently unordered, so you would need a column to specify the ordering.
To handle multiple names, I'd just get the list using a query such as:
select distinct 'max(case when name = '''+name+''' then apt end) as '+name+','
from t;
And copy the results into the query.

Pivot Assistance Required

I have a table like this . Table 1.
Customer Product Value Quantity Order Number
Dave Product 1 15 1 154
Dave Product 2 25 5 154
Dave Product 3 45 4 15
Rob Product 2 222 33 233
Now I want this , Table 2.
Customer Product 1 Quantity Product 1 Value Price per item ( Value /Quantity) for Product 1 Product 2 Quantity Product 2 Value Price per item ( Value /Quantity) for Product 2 Product 3 Quantity Product 3 Value Price per item ( Value /Quantity) for Product 3 Order Number
Dave 1 15 15 5 25 5 null null null 154
Dave null null null null null Null 4 45 11.25 15
Rob null null null 33 222 6.727272727 null null null 233
I was thinking about some pivot but wasn't sure how to construct it . Also the number of products is not fixed and change in Table 1.
In order to get the result, I would advise applying both an unpivot and a pivot to the data.
The UNPIVOT will convert the column data from your table into rows. Once the data is unpivoted, then you can apply a pivot.
Since you are using SQL Server 2008+ you can use CROSS APPLY with the VALUES clause to unpivot. Prior to 2008, you could use the UNPIVOT function. The code to unpivot the data is:
select t.customer,
replace(t.product, ' ', '')+'_'+c.col piv_col,
c.val,
t.ordernumber
from table1 t
cross apply
(
values
('value', cast(value as varchar(10))),
('quantity', cast(quantity as varchar(10))),
('PricePerUnit', cast((value/quantity) *1.0 as varchar(10)))
) c (col, val);
See Demo. This converts the data into the following format:
| CUSTOMER | PIV_COL | VAL | ORDERNUMBER |
---------------------------------------------------------
| Dave | Product1_value | 15 | 154 |
| Dave | Product1_quantity | 1 | 154 |
| Dave | Product1_PricePerUnit | 15.0 | 154 |
| Dave | Product2_value | 25 | 154 |
You can see that the row for Dave order 154 has been turned into rows and I have created the new column names that will be used for the pivot (piv_col). This column has concatenated the Product Name to the from of the previous column headers (value, quantity).
Since the data is in a single row, you can easily apply the pivot function to the data. The final code will be:
select customer,
Product1_quantity, Product1_value, Product1_PricePerUnit,
Product2_quantity, Product2_value, Product2_PricePerUnit,
Product3_quantity, Product3_value, Product3_PricePerUnit,
orderNumber
from
(
select t.customer,
replace(t.product, ' ', '')+'_'+c.col piv_col,
c.val,
t.ordernumber
from table1 t
cross apply
(
values
('value', cast(value as varchar(10))),
('quantity', cast(quantity as varchar(10))),
('PricePerUnit', cast((value/quantity) *1.0 as varchar(10)))
) c (col, val)
) d
pivot
(
max(val)
for piv_col in(Product1_quantity, Product1_value, Product1_PricePerUnit,
Product2_quantity, Product2_value, Product2_PricePerUnit,
Product3_quantity, Product3_value, Product3_PricePerUnit)
) piv;
See SQL Fiddle with Demo.
The above works great if you have a known number of products, but if not, then you will need to use dynamic SQL.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(replace(t.product, ' ', '')+'_'+c.col)
from Table1 t
cross apply
(
values ('value', 1), ('quantity', 0),('PricePerUnit', 3)
) c (col, so)
group by product, col, so
order by product, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT customer, ' + #cols + ', ordernumber
from
(
select t.customer,
replace(t.product, '' '', '''')+''_''+c.col piv_col,
c.val,
t.ordernumber
from table1 t
cross apply
(
values
(''value'', cast(value as varchar(10))),
(''quantity'', cast(quantity as varchar(10))),
(''PricePerUnit'', cast((value/quantity) *1.0 as varchar(10)))
) c (col, val)
) d
pivot
(
max(val)
for piv_col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. These queries give the result:
| CUSTOMER | PRODUCT1_QUANTITY | PRODUCT1_VALUE | PRODUCT1_PRICEPERUNIT | PRODUCT2_QUANTITY | PRODUCT2_VALUE | PRODUCT2_PRICEPERUNIT | PRODUCT3_QUANTITY | PRODUCT3_VALUE | PRODUCT3_PRICEPERUNIT | ORDERNUMBER |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Dave | (null) | (null) | (null) | (null) | (null) | (null) | 4 | 45 | 11.0 | 15 |
| Dave | 1 | 15 | 15.0 | 5 | 25 | 5.0 | (null) | (null) | (null) | 154 |
| Rob | (null) | (null) | (null) | 33 | 222 | 6.0 | (null) | (null) | (null) | 233 |