T-SQL: from rows to columns but not an actual pivot - sql-server

Is there a T-SQL (SQL Server 2008R2) query to transform TABLE_1 into the expected resultset?
TABLE_1
+----------+-------------------------+---------+------+
| IdDevice | Timestamp | M300 | M400 |
+----------+-------------------------+---------+------+
| 3 | 2012-12-05 16:29:51.000 | 2357,69 | 520 |
| 6 | 2012-12-05 16:29:51.000 | 1694,81 | 470 |
| 1 | 2012-12-05 16:29:51.000 | 2046,33 | 111 |
+----------+-------------------------+---------+------+
Expected resultset
+-------------------------+---------+--------+---------+--------+---------+--------+
| Timestamp | 3_M300 | 3_M400 | 6_M300 | 6_M400 | 6_M300 | 6_M400 |
+-------------------------+---------+--------+---------+--------+---------+--------+
| 2012-12-05 16:29:51.000 | 2357,69 | 520 | 1694,81 | 470 | 2046,33 | 111 |
+-------------------------+---------+--------+---------+--------+---------+--------+

This is still a PIVOT query but before you PIVOT you must perform an UNPIVOT of your columns.
First, you perform the UNPIVOT which takes your current multiple columns and transforms them into two columns - one with the value and the other with the column name. The key for the UNPIVOT is that the datatypes be the same, so in the subquery I cast any columns to the same datatype:
select timestamp,
value, cast(iddevice as varchar(10)) + '_'+col as col
from
(
select iddevice,
timestamp,
cast(m300 as varchar(10)) m300,
cast(m400 as varchar(10)) m400
from yourtable
) src
unpivot
(
value
for col in (m300, m400)
) unpiv
See SQL Fiddle with Demo
Result:
| TIMESTAMP | VALUE | COL |
------------------------------------------------------
| December, 05 2012 16:29:51+0000 | 2357,69 | 3_m300 |
| December, 05 2012 16:29:51+0000 | 520 | 3_m400 |
| December, 05 2012 16:29:51+0000 | 1694,81 | 6_m300 |
| December, 05 2012 16:29:51+0000 | 470 | 6_m400 |
| December, 05 2012 16:29:51+0000 | 2046,33 | 1_m300 |
| December, 05 2012 16:29:51+0000 | 111 | 1_m400 |
Once you complete the unpivot, then you can apply the PIVOT function:
select *
from
(
select timestamp,
value, cast(iddevice as varchar(10)) + '_'+col as col
from
(
select iddevice,
timestamp,
cast(m300 as varchar(10)) m300,
cast(m400 as varchar(10)) m400
from yourtable
) src
unpivot
(
value
for col in (m300, m400)
) unpiv
) src1
pivot
(
max(value)
for col in ([3_m300], [3_m400],
[6_m300], [6_m400],
[1_m300], [1_m400])
) piv
See SQL Fiddle with Demo
Results:
| TIMESTAMP | 3_M300 | 3_M400 | 6_M300 | 6_M400 | 1_M300 | 1_M400 |
--------------------------------------------------------------------------------------------
| December, 05 2012 16:29:51+0000 | 2357,69 | 520 | 1694,81 | 470 | 2046,33 | 111 |
If you have an unknown number of IdDevices that you want to transform into columns, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT DISTINCT ','
+ quotename(cast(t.IdDevice as varchar(10)) +'_'
+c.name)
from yourtable t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('IdDevice', 'Timestamp')
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT timestamp,' + #cols + ' from
(
select timestamp,
value, cast(iddevice as varchar(10)) + ''_''+col as col
from
(
select iddevice,
timestamp,
cast(m300 as varchar(10)) m300,
cast(m400 as varchar(10)) m400
from yourtable
) src
unpivot
(
value
for col in (m300, m400)
) unpiv
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
Edit, if you need a totals field for each m value, then you can use:
select timestamp,
[3_m300], [3_m400],
[6_m300], [6_m400],
[1_m300], [1_m400],
[1_m300] + [3_m300] + [6_m300] Total_m300,
[1_m400] + [3_m400] + [6_m400] Total_m400
from
(
select timestamp,
value, cast(iddevice as varchar(10)) + '_'+col as col
from
(
select iddevice,
timestamp,
m300,
m400
from yourtable
) src
unpivot
(
value
for col in (m300, m400)
) unpiv
) src1
pivot
(
sum(value)
for col in ([3_m300], [3_m400],
[6_m300], [6_m400],
[1_m300], [1_m400])
) piv
See SQL Fiddle with Demo

Related

SQL Server with non-alphabetic value as column name

I am building a pivot query inside a CTE. I have a table Table_1:
Store Count xCount
------- ---- ------
101 1 138
109 1 59
101 2 282
109 2 97
105 3 60
109 3 87
105 4.a 60
109 4.b 87
In Table_1, datatype of column count is varchar(10).
I used dynamic pivot query to pivot Table_1
DECLARE #DynamicCol AS NVARCHAR(MAX),
#SQL AS NVARCHAR(MAX)
select #DynamicCol = STUFF((SELECT distinct ',' + QUOTENAME(count)
from table_1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #SQL = ';WITH CTE as (
SELECT store,' + #DynamicCol + ' from
(
select * from table_1
) res
pivot
(
MAX(xCount)
for Count in (' + #DynamicCol + ')
) piv ) SELECT *
FROM CTE where 4.a is null'
execute(#SQL);
and get result as :
| STORE | 1 | 2 | 3 | 4.a |
+-------+-----+-----+-----+-----+
| 101 | 138 | 282 | null| null|
| 105 | null| null| 60 | 60 |
| 109 | 59 | 97 | 87 | 87 |
Now, I tried to get data from column 3 and 4.a where 3 and 4.a is null.
The query I build to get data is
Select * from CTE where 3 is null
Select * from CTE where 4.a is null
Also i tried to use this inside case statement as :
Select *,case when (3 is null) then 'some result' else '' end from CTE
In every query I am not getting any value returned by queries.
I tried by to append 'X' in each pivoted column and remove '.' from column anme, like column name looks like
| STORE | X1 | X2 | X3 | X4a |
+-------+-----+-----+-----+-----+
| 101 | 138 | 282 | null| null|
| 105 | null| null| 60 | 60 |
| 109 | 59 | 97 | 87 | 87 |
I am not able to query for this. Could anyone help me or suggest me any other idea to get data using above mentioned query ?
You have to wrap identifier that starts with digit with []:
DECLARE #DynamicCol AS NVARCHAR(MAX),
#SQL AS NVARCHAR(MAX)
select #DynamicCol = STUFF((SELECT distinct ',' + QUOTENAME(count)
from table_1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #SQL = ';WITH CTE as (
SELECT store,' + #DynamicCol + ' from
(
select * from table_1
) res
pivot
(
MAX(xCount)
for Count in (' + #DynamicCol + ')
) piv ) SELECT *
FROM CTE where [4.a] is null' -- here
execute(#SQL);

Convert Access TRANSFORM/PIVOT query to SQL Server with multiple table [duplicate]

TRANSFORM Avg(CASE WHEN [temp].[sumUnits] > 0
THEN [temp].[SumAvgRent] / [temp].[sumUnits]
ELSE 0
END) AS Expr1
SELECT [temp].[Description]
FROM [temp]
GROUP BY [temp].[Description]
PIVOT [temp].[Period];
Need to convert this query for sql server
I have read all other posts but unable to convert this into the same
Here is the equivalent version using the PIVOT table operator:
SELECT *
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN(Period1, Period2, Period3)
) p;
SQL Fiddle Demo
For instance, this will give you:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |
Note that When using the MS SQL Server PIVOT table operator, you have to enter the values for the pivoted column. However, IN MS Access, This was the work that TRANSFORM with PIVOT do, which is getting the values of the pivoted column dynamically. In this case you have to do this dynamically with the PIVOT operator, like so:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct
',' +
QUOTENAME(Period)
FROM temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query = ' SELECT Description, ' + #cols + '
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN( ' + #cols + ')
) p ';
Execute(#query);
Updated SQL Fiddle Demo
This should give you the same result:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |

Return column names based on which holds the maximum value in the record

I have a table with the following structure ...
+--------+------+------+------+------+------+
| ID | colA | colB | colC | colD | colE | [...] etc.
+--------+------+------+------+------+------+
| 100100 | 15 | 100 | 90 | 80 | 10 |
+--------+------+------+------+------+------+
| 100200 | 10 | 80 | 90 | 100 | 10 |
+--------+------+------+------+------+------+
| 100300 | 100 | 90 | 10 | 10 | 80 |
+--------+------+------+------+------+------+
I need to return a concatenated value of column names which hold the maximum 3 values per row ...
+--------+----------------------------------+
| ID | maxCols |
+--------+----------------------------------+
| 100100 | colB,colC,colD |
+--------+------+------+------+------+------+
| 100200 | colD,colC,colB |
+--------+------+------+------+------+------+
| 100300 | colA,colB,colE |
+--------+------+------+------+------+------+
It's okay to not concatenate the column names, and have maxCol1 | maxCol2 | maxCol3 if that's simpler
The order of the columns is important when concatenating them
The number of columns is limited and not dynamic
The number of rows is many
You could use UNPIVOT and get TOP 3 for each ID
;with temp AS
(
SELECT ID, ColValue, ColName
FROM #SampleData sd
UNPIVOT
(
ColValue For ColName in ([colA], [colB], [colC], [colD], [colE])
) unp
)
SELECT sd.ID, ca.ColMax
FROM #SampleData sd
CROSS APPLY
(
SELECT STUFF(
(
SELECT TOP 3 WITH TIES
',' + t.ColName
FROM temp t
WHERE t.ID = sd.ID
ORDER BY t.ColValue DESC
FOR XML PATH('')
)
,1,1,'') AS ColMax
) ca
See demo here: http://rextester.com/CZCPU51785
Here is one trick to do it using Cross Apply and Table Valued Constructor
SELECT Id,
maxCols= Stuff(cs.maxCols, 1, 1, '')
FROM Yourtable
CROSS apply(SELECT(SELECT TOP 3 ',' + NAME
FROM (VALUES (colA,'colA'),(colB,'colB'),(colC,'colC'),
(colD,'colD'),(colE,'colE')) tc (val, NAME)
ORDER BY val DESC
FOR xml path, type).value('.[1]', 'nvarchar(max)')) cs (maxCols)
If needed it can be made dynamic using Information_schema.Columns

Average values given times and make a pivot table

I have a table like this:
time | sensor | value |
---------------------------
10:00:00 | 1 | 12.3 |
10:00:00 | 2 | 14.3 |
10:00:00 | 3 | 15.3 |
10:00:01 | 1 | 15.3 |
10:00:02 | 2 | 11.3 |
10:00:01 | 3 | 10.3 |
10:00:03 | 1 | 9.3 |
10:00:03 | 2 | 15.3 |
10:00:04 | 3 | 11.3 |
...
10:05:01 | 1 | 15.3 |
10:05:02 | 2 | 11.3 |
10:05:01 | 3 | 10.3 |
10:05:03 | 1 | 9.3 |
10:05:03 | 2 | 15.3 |
10:05:04 | 3 | 11.3 |
And I need a Pivot table where the measurements are averaged in time intervals of lets say one minute, and ordered by sensor:
time | sensor 1| sensor 2| sensor 3|
----------------------------------------
10:00:00 | 11 | 12 | 10 |
10:01:00 | 10 | 13 | 15 |
...
I learned how to do this in MySQL, but now I am forced to use SQL Server and apparently this is completely different to MySQL.
Any help is appreciated, also some non trivial tutorial on this would be helpful.
EDIT
Attempt after the reply from Giorgi Nakeuri:
DECLARE #cols VARCHAR(MAX)
SELECT STUFF((SELECT '],[' + CAST([Group] AS VARCHAR(10)) FROM tblGroups
GROUP BY [Group]
FOR XML PATH('')), 1, 2, '') + ']'
DECLARE #s VARCHAR(MAX) = 'with cte as(
SELECT tblData.Timestmp, tblSeries.GroupID as SensorID, tblData.SeriesID, tblData.DataValue
FROM tblData
INNER JOIN tblSeries ON tblData.SeriesID = tblSeries.SeriesID
WHERE (tblSeries.[SeriesName] = ' + 'ActivePower'
+ 'AND tblData.Timestmp > ' + '2015-02-05 00:00:00' + ' AND tblData.Timestmp < ' + '2015-02-05 23:59:59'
+ 'AND tblData.DataValue>100)
)
select * from cte
pivot(AVG(DataValue) for GroupID in(' + #cols + ' )) p'
EXEC(#s)
The result is:
[150],[151],[152],[154],[159],[160],[164],[165],[166],[167],[168],[169],[171],[172],[173],[174],[175],[176],[180],[181],[182],[184],[185],[186],[187],[188],[189],[191],[192],[193],[194],[195],[196],[197],[20],[201],[205],[206],[207],[208],[209],[21],[210],[217],[218],[219],[220],[221],[222],[223],[224],[225],[226],[227],[228],[229],[231],[232],[233],[236],[237],[249],[251],[252],[253],[254],[258],[259],[260],[262],[263],[276],[277],[278],[279],[281],[288],[289],[290],[291],[293],[294],[295],[296],[300],[301],[302],[304],[308],[309],[314],[315],[316],[317],[324],[326],[329],[330],[331],[332],[333],[334],[335],[339],[340],[344],[347],[348],[351],[352],[353],[354],[355],[356],[357],[359],[370],[372],[373],[374],[375],[376],[380],[381],[382],[383],[384],[385],[386],[387],[388],[389],[390],[394],[395],[396],[397],[398],[400],[404],[405],[406],[407],[408],[409],[410],[411],[412],[413],[414],[418],[419],[420],[421],[425],[432],[435],[436],[437],[438],[439],[443],[444],[445],[452],[453],[457],[465],[466],[467],[468],[484],[485],[486],[487],[494],[495],[498],[499],[501],[506],[507],[511],[515],[516],[520],[521],[523],[527],[530],[531],[532],[533],[548],[550],[601],[605],[614],[615],[617],[81],[82],[829]
This are the sensor ID's, but not the Pivot table...
Try this:
select * from someTable
pivot(AVG(value) for sensor in([1],[2],[3])) p
If you have more columns in your table that you have showed then:
with cte as(select time, sensor, value from someTable)
select * from cte
pivot(AVG(value) for sensor in([1],[2],[3])) p
Dynamic version:
DECLARE #cols VARCHAR(MAX) = ''
SELECT #cols = STUFF((SELECT '],[' + CAST([Group] AS VARCHAR(10)) FROM tblGroups
WHERE [Group] <> 'meteo'
GROUP BY [Group]
FOR XML PATH('')), 1, 2, '') + ']'
DECLARE #s VARCHAR(MAX) = 'with cte as(
SELECT DATEADD(mi, datediff(mi, 0, tblData.Timestmp), 0) Timestmp, tblSeries.GroupID as SensorID, tblData.DataValue
FROM tblData INNER JOIN tblSeries ON tblData.SeriesID = tblSeries.SeriesID
WHERE tblSeries.[SeriesName] = ' + '''ActivePower''' + ' AND tblData.Timestmp > ''' + '2015-02-05 00:00:00''' + '
AND tblData.Timestmp < ' + '''2015-02-05 23:59:59''' + ' AND tblData.DataValue>100
)
select * from cte
pivot(AVG(DataValue) for GroupID in(' + #cols + ' )) p'
EXEC( #s)

sql continuous column values from data for pivot table view

I have data in a table in this structure:
Region | Date | Value
A | 01/01/2014 | 100
A | 01/20/2014 | 50
A | 01/02/2014 | 200
A | 01/05/2014 | 300
B | 01/01/2014 | 50
B | 02/15/2014 | 70
B | 02/25/2014 | 50
C | 05/02/2014 | 70
I am trying to create a pivot view like this using T-SQL queries:
Region | Jan-2014 | Feb-2014 | Mar-014 | Apr-2014 | May-2014 | -> thru desired month-year
A | 150 | 200 | 0 | 0 | 300 |
B | 50 | 120 | 0 | 0 | 0 |
C | 0 | 0 | 0 | 0 | 70 |
Please note, multiple values in the same month for a given region needs to be aggregated
Months that have no records should still show up as columns with zero values (Ex: March and April)
I tired using pivot options, withroll up etc., -- but can't seem to get this to work
any help is much appreciated..
Thank you.
The reason why you do not have results for those months is because you are missing the dates that you are pivoting into columns.
There are a few ways that you can do this. You can hard code all of the date values in the IN portion of your query so the columns will appear:
select Region,
isnull([Jan-2014], 0) [Jan-2014], isnull([Feb-2014], 0) [Feb-2014],
isnull([Mar-2014], 0) [Mar-2014], isnull([Apr-2014], 0) [Apr-2014],
isnull([May-2014], 0) [May-2014], isnull([Jun-2014], 0) [Jun-2014],
isnull([Jul-2014], 0) [Jul-2014], isnull([Aug-2014], 0) [Aug-2014],
isnull([Sep-2014], 0) [Sep-2014], isnull([Oct-2014], 0) [Oct-2014],
isnull([Nov-2014], 0) [Nov-2014], isnull([Dec-2014], 0) [Dec-2014]
from
(
select left(datename(month, t.date), 3) +'-'
+ cast(year(t.date) as char(4)) monthYear,
t.region,
t.value
from yt t
) src
pivot
(
sum(value)
for monthYear in ([Jan-2014], [Feb-2014], [Mar-2014], [Apr-2014],
[May-2014], [Jun-2014], [Jul-2014], [Aug-2014],
[Sep-2014], [Oct-2014], [Nov-2014], [Dec-2014])
) piv;
See SQL Fiddle with Demo.
Another way to do this is to create a table of dates that you can use in your query. Once created, then you can use a LEFT JOIN to your table so you return all of the dates that you want to appear. You can either create a recursive query to generate this list in your PIVOT query, or populate a table with a list of dates. A recursive query will be similar to this:
;with dates (startDate, endDate) as
(
select min(date), cast('2014-12-31' as date)
from yt
union all
select dateadd(m, 1, startDate), enddate
from dates
where month(startDate) + 1 <= month(enddate)
)
select startDate
from dates;
See SQL Fiddle with Demo. If you join this to your PIVOT, then the query will be:
select *
from
(
select left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)) monthYear,
t.region,
t.value
from dates d
left join yt t
on month(d.startdate) = month(t.date)
and year(d.startdate) = year(t.date)
) src
pivot
(
sum(value)
for monthYear in ([Jan-2014], [Feb-2014], [Mar-2014], [Apr-2014],
[May-2014], [Jun-2014], [Jul-2014], [Aug-2014],
[Sep-2014], [Oct-2014], [Nov-2014], [Dec-2014])
) piv
where region is not null;
See SQL Fiddle with Demo. You could use this as a part of your PIVOT query, but you would still need to hand-code all of the dates in the IN clause for the PIVOT.
I am guessing that you want to use a dynamic SQL version of this query so the dates will change based on your needs. The dynamic version will be:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#startdate datetime = '2014-01-01',
#enddate datetime = '2014-12-01'
;with dates (startDate, endDate) as
(
select #startdate, #enddate
from yt
union all
select dateadd(m, 1, startDate), enddate
from dates
where dateadd(m, 1, startDate) <= enddate
)
select #cols = STUFF((SELECT ',' + QUOTENAME(left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)))
from dates d
-- where startdate >= '2014-01-01' and startdate <= '2014-06-01'
group by d.startdate
order by d.startdate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,''),
#colsNull = STUFF((SELECT ', isnull(' + QUOTENAME(left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)))+', 0) as '+QUOTENAME(left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)))
from dates d
-- where startdate >= '2014-01-01' and startdate <= '2014-06-01'
group by d.startdate
order by d.startdate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
from dates
set #query = 'SELECT region, ' + #colsNull + ' from
(
select left(datename(month, t.date), 3) +''-''
+ cast(year(t.date) as char(4)) monthYear,
t.region,
t.value
from yt t
) x
pivot
(
sum(value)
for monthyear in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. This version uses the recursive CTE to generate the list of dates that will be used in the dynamic sql string. Even though your table of data might not exist for the months displayed, you will still have a new column.
This gives a result:
| REGION | JAN-2014 | FEB-2014 | MAR-2014 | APR-2014 | MAY-2014 | JUN-2014 | JUL-2014 | AUG-2014 | SEP-2014 | OCT-2014 | NOV-2014 | DEC-2014 |
----------------------------------------------------------------------------------------------------------------------------------------------
| A | 650 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| B | 50 | 120 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| C | 0 | 0 | 0 | 0 | 70 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

Resources