Combining columns with no common element (pivot) in SQL Server - sql-server-2005

I am trying to find the best way to display some data.
My dataset looks like this:
Form var1_Day var1_WTD var1_30Day var2_Day var2_WTD var2_30Day ...
NA null null null 77 448 2581
A1 166 791 4842 null null null
A2 304 1312 8365 null null null
A3 29 113 656 null null null
I am trying to figure out how I would go about displaying the data like this:
Var Form Day WTD 30Day
var1 NA null null null
var1 A1 166 791 4842
var1 A2 304 1312 8365
var1 A3 29 113 656
var2 NA 77 448 2581
var2 A1 null null null
var2 A2 null null null
var2 A3 null null null
...
I believe I will have to use a pivot table but am not sure where to start.
Thank You,
Brad

While you can do this via a static pivot like the other answer, if you have a lot of columns to transform you can use a dynamic pivot:
DECLARE #colsUnPivot AS NVARCHAR(MAX),
#colsPivot AS NVARCHAR(MAX),
#colsUnPivotNull as NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #colsUnPivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('test') and
C.name like 'var%'
for xml path('')), 1, 1, '')
SET #colsUnPivotNull = stuff((select ', IsNull('+ quotename(C.name) +', 0)' + quotename(C.name)
from sys.columns as C
where C.object_id = object_id('test') and
C.name like 'var%'
for xml path('')), 1, 1, '')
SET #colsPivot = stuff((select DISTINCT ','+quotename(right((C.name), len(C.name)-5))
from sys.columns as C
where C.object_id = object_id('test') and
C.name like 'var%'
for xml path('')), 1, 1, '')
set #query
= '
SELECT var, form, '+ #colsPivot +'
FROM
(
SELECT distinct substring(field, 1, 4) var,
--field,
right((field), len(field)-5) as col,
form,
value
from
(
SELECT form, '+ #colsUnPivotNull + '
FROM test
) t1
unpivot
(
value
for field in (' + #colsUnPivot + ')
) unpvt
) x
pivot
(
sum(value)
for col in ('+ #colsPivot + ')
) p
ORDER BY var, form'
execute(#query)
See SQL Fiddle with Demo
This will get the list of columns to unpivot and the pivot at the time of execution. Then you will not have to hard code the values if you have a lot of columns.

Actually, you want UNPIVOT
Like so...
select SUBSTRING(t,1,4) as Var, form, day, wtd, 30day
from yourtable
unpivot ([day] for d in (var1_day,var2_day)) td
unpivot ([wtd] for w in (var1_wtd,var2_wtd)) tw
unpivot ([30day] for t in (var1_30day,var2_30day)) tt
where SUBSTRING(d,1,4) = SUBSTRING(w,1,4)
and SUBSTRING(d,1,4) = SUBSTRING(t,1,4)

Related

Restructure table by removing NULL values

I have a table in SQL that looks like this:
Customer Product 1999 2000 2001 2002 2003
Smith 51 NULL NULL 15 14 NULL
Jones 14 11 7 NULL NULL NULL
Jackson 13 NULL NULL NULL 3 9
The figures under each year column are amounts, in dollars. Each customer has two consecutive years of amounts, and the rest of the years are zero. I would like to re-structure this table so that instead of wide list of years, it just has two columns Amount-Year1 and Amount-Year2. So it selects the two non-zero years and puts them in those columns, in the correct order. This would greatly reduce the size of my table.
So far I've been able to re-structure it so that there is one amount column and one year column, but I then get multiple rows per customer, which unfortunately I can't have (due to downstream analysis). Can anyone think of a way to get the two Amount-Year columns?
I would like the final table to look like this:
Customer Product Amount_Y1 Amount_Y2
Smith 51 15 14
Jones 14 11 7
Jackson 13 3 9
I don't mind that I lose the information about the specific years, as I can get that from another source. The actual table has data for all years between 1999 and 2018, and there will be further years in the future.
Thanks
Thankfully UNPIVOT removes NULLs anyway, so we can do this with UNPIVOT/ROWNUMBER(),PIVOT:
declare #t table (Customer varchar(15),Product int,[1999] int,
[2000] int,[2001] int,[2002] int,[2003] int)
insert into #T(CUstomer,Product,[1999],[2000],[2001],[2002],[2003]) values
('Smith' ,51,NULL,NULL, 15, 14,NULL),
('Jones' ,14, 11, 7,NULL,NULL,NULL),
('Jackson',13,NULL,NULL,NULL, 3, 9)
;With Numbered as (
select
Customer,Product,Value,
ROW_NUMBER() OVER (PARTITION BY Customer,Product
ORDER BY Year) rn
from
#t t
unpivot
(Value for Year in ([1999],[2000],[2001],[2002],[2003])) u
)
select
*
from
Numbered n
pivot
(SUM(Value) for rn in ([1],[2])) w
Results:
Customer Product 1 2
--------------- ----------- ----------- -----------
Jackson 13 3 9
Jones 14 11 7
Smith 51 15 14
Use COALESCE that will do the job for you. The query is dynamics so that if tomorrow year columns are changed, i.e. removed or added you do not have to change anything.
Sample query: (Assuming table to be table1 and column names to be same as year).
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM Table1'
EXEC(#sql)
If you're dealing with a temporary table, then the code will change slightly:
Test it here: http://rextester.com/MRVR48808
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM #Table1' --Changes here
EXEC(#sql)
Try using COALESCE as follows : For one field from beginning to end and for the second in the reverse manner.
SELECT Customer,Product, COALESCE([1999],[2000],[2001],[2002],[2003]) as Y1,
COALESCE([2003],[2002],[2001],[2000],[1999]) as Y2
FROM #TEMPDATA
I would do this using cross apply:
select t.customer, t.product, v.Amount_Y1, v.Amount_Y2
from t cross apply
(select max(case when which = 1 then val end) as Amount_Y1,
max(case when which = 2 then val end) as Amount_Y2
from (select val, yr, row_number() over (order by yr) as which
from (values (t.[1999], 1999), (t.[2000], 2000), (t.[2001], 2001),
(t.[2002], 2002), (t.[2003], 2003)
) v(val, yr)
where val is not null
) v

Using Dynamic SQL to reformat a table to having column names as rows

I've been stuck with this for a while and I've not found something on the website that answers something like this so please point me to the right direction if an existing question exists. In SQL Server 2012, I have a table with ID as the primary key:
ID col1 col2 col3 ....
--- ---- ----- -----
1 a z k
2 g b p
3 k d a
I don't know the length of the table nor the amount of columns/ column names
but I want to be able to get a table that gives me something like:
ID ColName Value
--- ---- -----
1 col1 a
1 col2 z
1 col3 k
2 col1 g
2 col2 b
2 col3 p
3 col1 k
3 col2 d
3 col3 a
...
I know that
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'table'
gets me my columns and I've tried trying to use that to create a temp table to
insert my desired format into a temp table but I'm not sure how to go through each row in a table and then grab the desired values dynamically for each column name and display it. I've been able to kind of achieve this with double cursors but that is painfully slow and I'm not sure how else to approach this since I'm relatively new at SQL. Any help would be appreciated!
Edit
Thank you so very much Lamak! I did have varying data types and coverting them to varchars for now shows me that the concept does work. However, I have 4 common datatypes (varchar, float, int, datetime) that I want to account for so I have 4 value fields for each of those where I would insert the column value into one of those 4 depending on it and leave the other 3 blank. I know that INFORMATION_SCHEMA.COLUMNS also provides the data_types so I was wondering what the syntax would be to convert the datatype in the "STUFF" variables based on a simple IF statement. I tried mapping the data_types to the column names but having any type of conditional statement breaks the query. If anyone has a simple example, that would be great :)
Edit
Actually, I've been able to figure out that I would need to create 4 variables to each data type rather than do them all in just one of them. Thank you all for your help!
As the comments said, you'll need to use dynamic unpivot.
If every column aside ID have the same datatype, you can use the following query:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #colsUnpivot = STUFF((SELECT ', ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SET #query = '
SELECT ID,
ColName,
Value
FROM
(
SELECT *
FROM dbo.table
) x
UNPIVOT
(
Value FOR ColName IN (' + #colsunpivot + ')
) u
';
EXEC(#query);
Now, if the datatypes are different, then you'll need to first convert every column to a common datatype. In the following example, I'll use NVARCHAR(1000), but you'll need to convert them to the right datatype:
DECLARE #colsUnpivot1 AS NVARCHAR(MAX),
#colsUnpivot2 as NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #colsUnpivot1 = STUFF((SELECT ', ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SELECT #colsUnpivot2 = STUFF((SELECT ', CONVERT(NVARCHAR(1000),' + QUOTENAME(C.name)
+ ') ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SET #query = '
SELECT ID,
ColName,
Value
FROM
(
SELECT ID, ' + #colsUnpivot2 + '
FROM dbo.table
) x
UNPIVOT
(
Value FOR ColName IN ('+ #colsunpivot1 +')
) u
';
EXEC(#query);
You don't have to go DYNAMIC. Another Option with with a CROSS APPLY and a little XML.
UnPivot is more performant, but you will find the performance of this approach very respectable.
An added bonus of this approach is that the From #YourTable A could be any query (not limited to a table). For example From ( -- Your Complex Query --) A
Declare #YourTable table (ID int,Col1 varchar(25),Col2 varchar(25),Col3 varchar(25))
Insert Into #YourTable values
(1,'a','z','k')
,(2,'g','b','p')
,(3,'k','d','a')
Select C.*
From #YourTable A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select ID = r.value('#ID','int')
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','OtherFieldsToExclude')
) C
Returns
ID Item Value
1 Col1 a
1 Col2 z
1 Col3 k
2 Col1 g
2 Col2 b
2 Col3 p
3 Col1 k
3 Col2 d
3 Col3 a

SQL Query rows need to be converted to columns dynamically

Please help me to solve my below query -
I have the following data in my table-
Agent Variable Chandigarh NewDelhi
ABC Leads 102.00 10
ABC TotalTime 10.52 1
ABC RPH 22.79 22
ABC TotalRev 239.70 23
XYZ Leads 14.00 14
XYZ TotalTime 1.52 1
XYZ RPH 21.64 21
XYZ TotalRev 32.90 32
I want the solution like this
Agent Chandigarh_Leads Chandigarh_TotalTime Chandigarh_RPH Chandigarh_RPH_TotalRev NewDelhi_Leads .......
ABC 102.00 10.52 22.79 239.70 10 .......
XYZ 14 1.52 21.64 32.90 14 ............
FYI, I can have more states in columns, it has no limits it may be 10 or 20 or 5 etc. So i need result dynamic query. Please help me, is it possible without static query?
Dynamic SQL + pivoting:
DECLARE #sql nvarchar(max),
#columns nvarchar(max),
#col_to_cast nvarchar(max),
#col_unpvt nvarchar(max)
--This will give:
--,[Chandigarh_Leads],[Chandigarh_RPH]....[NewDelhi_TotalRev],[NewDelhi_TotalTime]
SELECT #columns = COALESCE(#columns,'')+',['+name+'_'+Variable +']'
FROM (
SELECT DISTINCT Variable
FROM #yourtable) v
CROSS JOIN (
SELECT name
FROM sys.columns
WHERE object_id = OBJECT_ID(N'#yourtable')
AND name not in ('Agent', 'Variable')
) c
ORDER BY c.name, v.Variable
--As columns while unpivoting must be same type we need to cast them in same datattype:
--This will give
--,CAST([Chandigarh] as float) as [Chandigarh],CAST([NewDelhi] as float) as [NewDelhi]
SELECT #col_to_cast = COALESCE(#col_to_cast,'')+',CAST(' + QUOTENAME(name)+ ' as float) as '+ QUOTENAME(name)
#col_unpvt = COALESCE(#col_unpvt,'') + ','+ QUOTENAME(name)
FROM sys.columns
WHERE object_id = OBJECT_ID(N'#yourtable')
AND name not in ('Agent', 'Variable')
SELECT #sql = N'
SELECT *
FROM (
SELECT Agent,
[Columns]+''_''+Variable as ColName,
[Values] as ColVal
FROM (
SELECT Agent,
Variable'+#col_to_cast+'
FROM #yourtable
) p
UNPIVOT (
[Values] FOR [Columns] IN ('+STUFF(#col_unpvt,1,1,'')+')
) unpvt
) t
PIVOT (
MAX(ColVal) FOR ColName IN ('+STUFF(#columns,1,1,'')+')
) pvt'
EXEC sp_executesql #sql
Output:
Agent Chandigarh_Leads Chandigarh_RPH Chandigarh_TotalRev Chandigarh_TotalTime NewDelhi_Leads NewDelhi_RPH NewDelhi_TotalRev NewDelhi_TotalTime
ABC 102 22,79 239,7 10,52 10 22 23 1
XYZ 14 21,64 32,9 1,52 14 21 32 1

sql server - making a result vertical to horizontal [duplicate]

Apart from writing the cursor reading each rows and populating it into columns, any other alternative if I need to transpose each rows into columns ?
TimeSeconds TagID Value
1378700244 A1 3.75
1378700245 A1 30
1378700304 A1 1.2
1378700305 A2 56
1378700344 A2 11
1378700345 A3 0.53
1378700364 A1 4
1378700365 A1 14.5
1378700384 A1 144
1378700384 A4 10
The number of columns are not fixed.
Output : I just assigned n/a as a placeholder for no data in that intersection.
TimeSec A1 A2 A3 A4
1378700244 3.75 n/a n/a n/a
1378700245 30 n/a n/a n/a
1378700304 1.2 n/a n/a n/a
1378700305 n/a 56 n/a n/a
1378700344 n/a 11 n/a n/a
1378700345 n/a n/a 0.53 n/a
1378700364 n/a n/a n/a 4
1378700365 14.5 n/a n/a n/a
1378700384 144 n/a n/a 10
Hope you can share with me some tips. Thanks.
One way to do it if tagID values are known upfront is to use conditional aggregation
SELECT TimeSeconds,
COALESCE(MAX(CASE WHEN TagID = 'A1' THEN Value END), 'n/a') A1,
COALESCE(MAX(CASE WHEN TagID = 'A2' THEN Value END), 'n/a') A2,
COALESCE(MAX(CASE WHEN TagID = 'A3' THEN Value END), 'n/a') A3,
COALESCE(MAX(CASE WHEN TagID = 'A4' THEN Value END), 'n/a') A4
FROM table1
GROUP BY TimeSeconds
or if you're OK with NULL values instead of 'n/a'
SELECT TimeSeconds,
MAX(CASE WHEN TagID = 'A1' THEN Value END) A1,
MAX(CASE WHEN TagID = 'A2' THEN Value END) A2,
MAX(CASE WHEN TagID = 'A3' THEN Value END) A3,
MAX(CASE WHEN TagID = 'A4' THEN Value END) A4
FROM table1
GROUP BY TimeSeconds
or with PIVOT
SELECT TimeSeconds, A1, A2, A3, A4
FROM
(
SELECT TimeSeconds, TagID, Value
FROM table1
) s
PIVOT
(
MAX(Value) FOR TagID IN (A1, A2, A3, A4)
) p
Output (with NULLs):
TimeSeconds A1 A2 A3 A4
----------- ------- ------ ----- -----
1378700244 3.75 NULL NULL NULL
1378700245 30.00 NULL NULL NULL
1378700304 1.20 NULL NULL NULL
1378700305 NULL 56.00 NULL NULL
1378700344 NULL 11.00 NULL NULL
1378700345 NULL NULL 0.53 NULL
1378700364 4.00 NULL NULL NULL
1378700365 14.50 NULL NULL NULL
1378700384 144.00 NULL NULL 10.00
If you have to figure TagID values out dynamically then use dynamic SQL
DECLARE #cols NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(TagID)
FROM Table1
ORDER BY 1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #sql = 'SELECT TimeSeconds, ' + #cols + '
FROM
(
SELECT TimeSeconds, TagID, Value
FROM table1
) s
PIVOT
(
MAX(Value) FOR TagID IN (' + #cols + ')
) p'
EXECUTE(#sql)
SQL Server has a PIVOT command that might be what you are looking for.
select * from Tag
pivot (MAX(Value) for TagID in ([A1],[A2],[A3],[A4])) as TagTime;
If the columns are not constant, you'll have to combine this with some dynamic SQL.
DECLARE #columns AS VARCHAR(MAX);
DECLARE #sql AS VARCHAR(MAX);
select #columns = substring((Select DISTINCT ',' + QUOTENAME(TagID) FROM Tag FOR XML PATH ('')),2, 1000);
SELECT #sql =
'SELECT *
FROM TAG
PIVOT
(
MAX(Value)
FOR TagID IN( ' + #columns + ' )) as TagTime;';
execute(#sql);
Another option that may be suitable in this situation is using XML
The XML option to transposing rows into columns is basically an optimal version of the PIVOT in that it addresses the dynamic column limitation. 
The XML version of the script addresses this limitation by using a combination of XML Path, dynamic T-SQL and some built-in functions (i.e. STUFF, QUOTENAME).
Vertical expansion
Similar to the PIVOT and the Cursor, newly added policies are able to be retrieved in the XML version of the script without altering the original script.
Horizontal expansion
Unlike the PIVOT, newly added documents can be displayed without altering the script.
Performance breakdown
In terms of IO, the statistics of the XML version of the script is almost similar to the PIVOT – the only difference is that the XML has a second scan of dtTranspose table but this time from a logical read – data cache.
You can find some more about these solutions (including some actual T-SQL exmaples) in this article:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/
Based on the solution from bluefeet here is a stored procedure that uses dynamic sql to generate the transposed table. It requires that all the fields are numeric except for the transposed column (the column that will be the header in the resulting table):
/****** Object: StoredProcedure [dbo].[SQLTranspose] Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for transposing.
-- Parameters: #TableName - Table to transpose
-- #FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- table and FIeldToTranspose should be written using single quotes
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose]
-- Add the parameters for the stored procedure here
#TableName NVarchar(MAX) = '',
#FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#queryPivot AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#columnToPivot as NVARCHAR(MAX),
#tableToPivot as NVARCHAR(MAX),
#colsResult as xml
select #tableToPivot = #TableName;
select #columnToPivot = #FieldNameTranspose
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(#tableToPivot) and
C.name <> #columnToPivot
for xml path('')), 1, 1, '')
set #queryPivot = 'SELECT #colsResult = (SELECT '',''
+ quotename('+#columnToPivot+')
from '+#tableToPivot+' t
where '+#columnToPivot+' <> ''''
FOR XML PATH(''''), TYPE)'
exec sp_executesql #queryPivot, N'#colsResult xml out', #colsResult out
select #colsPivot = STUFF(#colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query
= 'select name, rowid, '+#colsPivot+'
from
(
select '+#columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+#columnToPivot+' order by '+#columnToPivot+') as rowid
from '+#tableToPivot+'
unpivot
(
value for name in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for '+#columnToPivot+' in ('+#colsPivot+')
) piv
order by rowid'
exec(#query)
END
I had a slightly different requirement, whereby I had to selectively transpose columns into rows.
The table had columns:
create table tbl (ID, PreviousX, PreviousY, CurrentX, CurrentY)
I needed columns for Previous and Current, and rows for X and Y. A Cartesian product generated on a static table worked nicely, eg:
select
ID,
max(case when metric='X' then PreviousX
case when metric='Y' then PreviousY end) as Previous,
max(case when metric='X' then CurrentX
case when metric='Y' then CurrentY end) as Current
from tbl inner join
/* Cartesian product - transpose by repeating row and
picking appropriate metric column for period */
( VALUES (1, 'X'), (2, 'Y')) AS x (sort, metric) ON 1=1
group by ID
order by ID, sort

SQL Server : Transpose rows to columns

Apart from writing the cursor reading each rows and populating it into columns, any other alternative if I need to transpose each rows into columns ?
TimeSeconds TagID Value
1378700244 A1 3.75
1378700245 A1 30
1378700304 A1 1.2
1378700305 A2 56
1378700344 A2 11
1378700345 A3 0.53
1378700364 A1 4
1378700365 A1 14.5
1378700384 A1 144
1378700384 A4 10
The number of columns are not fixed.
Output : I just assigned n/a as a placeholder for no data in that intersection.
TimeSec A1 A2 A3 A4
1378700244 3.75 n/a n/a n/a
1378700245 30 n/a n/a n/a
1378700304 1.2 n/a n/a n/a
1378700305 n/a 56 n/a n/a
1378700344 n/a 11 n/a n/a
1378700345 n/a n/a 0.53 n/a
1378700364 n/a n/a n/a 4
1378700365 14.5 n/a n/a n/a
1378700384 144 n/a n/a 10
Hope you can share with me some tips. Thanks.
One way to do it if tagID values are known upfront is to use conditional aggregation
SELECT TimeSeconds,
COALESCE(MAX(CASE WHEN TagID = 'A1' THEN Value END), 'n/a') A1,
COALESCE(MAX(CASE WHEN TagID = 'A2' THEN Value END), 'n/a') A2,
COALESCE(MAX(CASE WHEN TagID = 'A3' THEN Value END), 'n/a') A3,
COALESCE(MAX(CASE WHEN TagID = 'A4' THEN Value END), 'n/a') A4
FROM table1
GROUP BY TimeSeconds
or if you're OK with NULL values instead of 'n/a'
SELECT TimeSeconds,
MAX(CASE WHEN TagID = 'A1' THEN Value END) A1,
MAX(CASE WHEN TagID = 'A2' THEN Value END) A2,
MAX(CASE WHEN TagID = 'A3' THEN Value END) A3,
MAX(CASE WHEN TagID = 'A4' THEN Value END) A4
FROM table1
GROUP BY TimeSeconds
or with PIVOT
SELECT TimeSeconds, A1, A2, A3, A4
FROM
(
SELECT TimeSeconds, TagID, Value
FROM table1
) s
PIVOT
(
MAX(Value) FOR TagID IN (A1, A2, A3, A4)
) p
Output (with NULLs):
TimeSeconds A1 A2 A3 A4
----------- ------- ------ ----- -----
1378700244 3.75 NULL NULL NULL
1378700245 30.00 NULL NULL NULL
1378700304 1.20 NULL NULL NULL
1378700305 NULL 56.00 NULL NULL
1378700344 NULL 11.00 NULL NULL
1378700345 NULL NULL 0.53 NULL
1378700364 4.00 NULL NULL NULL
1378700365 14.50 NULL NULL NULL
1378700384 144.00 NULL NULL 10.00
If you have to figure TagID values out dynamically then use dynamic SQL
DECLARE #cols NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(TagID)
FROM Table1
ORDER BY 1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #sql = 'SELECT TimeSeconds, ' + #cols + '
FROM
(
SELECT TimeSeconds, TagID, Value
FROM table1
) s
PIVOT
(
MAX(Value) FOR TagID IN (' + #cols + ')
) p'
EXECUTE(#sql)
SQL Server has a PIVOT command that might be what you are looking for.
select * from Tag
pivot (MAX(Value) for TagID in ([A1],[A2],[A3],[A4])) as TagTime;
If the columns are not constant, you'll have to combine this with some dynamic SQL.
DECLARE #columns AS VARCHAR(MAX);
DECLARE #sql AS VARCHAR(MAX);
select #columns = substring((Select DISTINCT ',' + QUOTENAME(TagID) FROM Tag FOR XML PATH ('')),2, 1000);
SELECT #sql =
'SELECT *
FROM TAG
PIVOT
(
MAX(Value)
FOR TagID IN( ' + #columns + ' )) as TagTime;';
execute(#sql);
Another option that may be suitable in this situation is using XML
The XML option to transposing rows into columns is basically an optimal version of the PIVOT in that it addresses the dynamic column limitation. 
The XML version of the script addresses this limitation by using a combination of XML Path, dynamic T-SQL and some built-in functions (i.e. STUFF, QUOTENAME).
Vertical expansion
Similar to the PIVOT and the Cursor, newly added policies are able to be retrieved in the XML version of the script without altering the original script.
Horizontal expansion
Unlike the PIVOT, newly added documents can be displayed without altering the script.
Performance breakdown
In terms of IO, the statistics of the XML version of the script is almost similar to the PIVOT – the only difference is that the XML has a second scan of dtTranspose table but this time from a logical read – data cache.
You can find some more about these solutions (including some actual T-SQL exmaples) in this article:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/
Based on the solution from bluefeet here is a stored procedure that uses dynamic sql to generate the transposed table. It requires that all the fields are numeric except for the transposed column (the column that will be the header in the resulting table):
/****** Object: StoredProcedure [dbo].[SQLTranspose] Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for transposing.
-- Parameters: #TableName - Table to transpose
-- #FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- table and FIeldToTranspose should be written using single quotes
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose]
-- Add the parameters for the stored procedure here
#TableName NVarchar(MAX) = '',
#FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#queryPivot AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#columnToPivot as NVARCHAR(MAX),
#tableToPivot as NVARCHAR(MAX),
#colsResult as xml
select #tableToPivot = #TableName;
select #columnToPivot = #FieldNameTranspose
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(#tableToPivot) and
C.name <> #columnToPivot
for xml path('')), 1, 1, '')
set #queryPivot = 'SELECT #colsResult = (SELECT '',''
+ quotename('+#columnToPivot+')
from '+#tableToPivot+' t
where '+#columnToPivot+' <> ''''
FOR XML PATH(''''), TYPE)'
exec sp_executesql #queryPivot, N'#colsResult xml out', #colsResult out
select #colsPivot = STUFF(#colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query
= 'select name, rowid, '+#colsPivot+'
from
(
select '+#columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+#columnToPivot+' order by '+#columnToPivot+') as rowid
from '+#tableToPivot+'
unpivot
(
value for name in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
sum(value)
for '+#columnToPivot+' in ('+#colsPivot+')
) piv
order by rowid'
exec(#query)
END
I had a slightly different requirement, whereby I had to selectively transpose columns into rows.
The table had columns:
create table tbl (ID, PreviousX, PreviousY, CurrentX, CurrentY)
I needed columns for Previous and Current, and rows for X and Y. A Cartesian product generated on a static table worked nicely, eg:
select
ID,
max(case when metric='X' then PreviousX
case when metric='Y' then PreviousY end) as Previous,
max(case when metric='X' then CurrentX
case when metric='Y' then CurrentY end) as Current
from tbl inner join
/* Cartesian product - transpose by repeating row and
picking appropriate metric column for period */
( VALUES (1, 'X'), (2, 'Y')) AS x (sort, metric) ON 1=1
group by ID
order by ID, sort