SQL query unknown rows into columns - sql

I asked this question and it was marked as a duplicate of How to pivot unknown number of columns & no aggregate in SQL Server?, but that answer doesn't help me.
I have a table of data that looks like this, with an unknown number of rows and values.
RecID Name Value
1 Color Red
2 Size Small
3 Weight 20lbs
4 Shape Square
I need a query that will return the data like this, building out a column for each row. I cannot hard code anything except the column headers 'Name' and 'Value'.
Color Size Weight Shape
Red Small 20lbs Square
Here is what I have so far that is partly working:
INSERT INTO #Table VALUES
(1,'Color' ,'Red'),
(2,'Size' ,'Small'),
(3,'Weight','20lbs'),
(4,'Shape' ,'Square')
;with mycte
as
(
SELECT rn,cols,val
FROM (SELECT row_number() over(order by Name) rn, Name, Value
FROM #Table) AS src1
UNPIVOT (val FOR cols
IN ( [Name], [Value])) AS unpvt
)
SELECT *
FROM (SELECT rn,cols,val
FROM mycte) AS src2 PIVOT
( Max(val) FOR rn IN ([1], [2], [3])) AS pvt
Which returns:
cols 1 2 3
Name Color Shape Size
Value Red Square Small
Two problems with this that I can't seem to resolve.
I don't need the column headers and the first column that has cols, Name, Value in it.
Can't figure out how to have it build a column for each row without specifying the [x] identifiers.
Any guidance would be great I've been stuck on this a while now.

declare #collist nvarchar(max)
SET #collist = stuff((select distinct ',' + QUOTENAME(name)
FROM #t -- your table here
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
declare #q nvarchar(max)
set #q = '
select *
from (
select rn, name, Value
from (
select *, row_number() over (partition by name order by RecID desc) as rn
from #t -- your table here
) as x
) as source
pivot (
max(Value)
for name in (' + #collist + ')
) as pvt
'
exec (#q)

Until now, I have reached to following code, hope it helps you,
Current outpu comes as
Color Shape Size Weight
Red NULL NULL NULL
NULL NULL Small NULL
NULL NULL NULL 20lbs
NULL Square NULL NULL
Create table DyTable
(
tid int,
Name varchar(20),
Value varchar(20)
)
INSERT INTO DyTable VALUES
(1,'Color' ,'Red'),
(2,'Size' ,'Small'),
(3,'Weight','20lbs'),
(4,'Shape' ,'Square')
DECLARE #Cols NVARCHAR(MAX);
DECLARE #Cols1 NVARCHAR(MAX);
SELECT #Cols = STUFF((
SELECT DISTINCT ', ' + QUOTENAME(Name)
FROM DyTable
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,2,'')
,#Cols1 = STUFF((
SELECT DISTINCT ', max(' + QUOTENAME(Name) + ') as ' + Name
FROM DyTable
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,2,'')
DECLARE #Sql NVARCHAR(MAX)
Select #Cols
SET #Sql = 'Select '+ #Cols1 +'
from (SELECT ' + #Cols + '
FROM DyTable t
PIVOT (MAX(Value)
FOR Name
IN (' + #Cols + ')
)P)a'
EXECUTE sp_executesql #Sql

Related

MS SQL Pivot dat from long with multiple columns

I want to pivot a table from long to wide that has multiple id columns.
I found solutions for one column but not really for multiple columns.
The closest solution that I could adapt for one column was this one
T-SQL PIVOT data from long form to wide by a date
My table looks more or less like this,
create table t (id int, date date, varA_id int, VarB_id int, value int)
insert into t values
(1,'2005-01-20',1, 1,197)
,(2,'2005-01-20',1,2,58)
,(3,'2005-01-20',1,3,90)
,(4,'2005-01-20',2,1,210)
,(5,'2005-01-20',2,2,133)
,(6,'2005-01-20',2,3,67)
,(7,'2005-01-20',3,1,87)
,(8,'2005-01-20',3,2,87)
,(9,'2005-01-20',3,3,87)
Actually without the date, but that's fine. I want to spread in a way that I get columns for each permutation of VarA_id and VarB_id
So my expected result would look like this
My actual table has three _id columns and more permutations, so I really need a generic solution.
Based on the other solution in my link I was hoping something like this would work. I adjust the top part that creates the column names and this would work. I dont know how to realy adjust the bottom part that fetches the values.
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + 'VarA_'+convert(varchar(10),varA_id) + '_VarB_'+convert(varchar(10),varB_id)
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,2,'')
select #sql = '
select Id, date, ' + #cols + '
from (
select Id, date, varA_id = ''v''+convert(varchar(10),varA_id), value
from t
) as t
pivot (sum([value]) for [varA_id] in (' + #cols + ') ) p'
select #sql
exec(#sql);
The main problem with your dynamic sql?
It was that the name constructed in the source query didn't match the generated column names.
Here's a fix :
declare #cols varchar(max) = null;
declare #sql nvarchar(max);
select #cols = concat(#cols+', '+char(10), quotename(concat('VarA_', varA_id, '_VarB_', varB_id)))
from test
group by varA_id, varB_id
order by varA_id, varB_id;
-- select #cols as cols;
set #sql = 'select * '+char(10)+
'from ( ' +char(10)+
' select [date], [value], ' +char(10)+
' concat(''VarA_'',varA_id,''_VarB_'',varB_id) as Col ' +char(10)+
' from test ' +char(10)+
') as src ' +char(10)+
'pivot (sum([value]) for Col in ('+char(10)+ #cols +char(10)+')) pvt';
-- select #sql as sql;
exec(#sql);
date
VarA_1_VarB_1
VarA_1_VarB_2
VarA_1_VarB_3
VarA_2_VarB_1
VarA_2_VarB_2
VarA_2_VarB_3
VarA_3_VarB_1
VarA_3_VarB_2
VarA_3_VarB_3
2005-01-20
197
58
90
210
133
67
87
87
87
db<>fiddle here
Ok my own solution so far is to add a help column and basically just do what the other questions does. I need to improve on this, so I dont add a column and I would like better names, but at least this shows what I want.
alter table t add help_col nvarchar(10)
Update t
set help_col=convert(varchar(10),varA_id)+convert(varchar(10),varB_id)
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + 'v'+convert(varchar(10),help_col)
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,2,'')
select #sql = '
select date, ' + #cols + '
from (
select date, help_col = ''v''+convert(varchar(10),help_col), value
from t
) as t
pivot (sum([value]) for [help_col] in (' + #cols + ') ) p'
select #sql
exec(#sql);
Which results in
select date, v11, v12, v13, v21, v22, v23, v31, v32, v33 from ( select date, help_col = 'v'+convert(varchar(10),help_col), value from t ) as t pivot (sum([value]) for [help_col] in (v11, v12, v13, v21, v22, v23, v31, v32, v33) ) p
which yields
date v11 v12 v13 v21 v22 v23 v31 v32 v33
2005-01-20 197 58 90 210 133 67 87 87 87

SQL Server Dynamic pivot for an unknow number of columns

The question has been asked before, but in a slightly different scenario (one that doesn't seem to fit to my question) so..
I have data that looks like this
Name |Item |Note
George|Paperclip |Two boxes
George|Stapler |blue one
George|Stapler |red one
George|Desk lamp |No light bulb
Mark |Paperclip |One box 2"
Mark |Paperclip |One box 4"
Mark |Block Notes|a blue one
..? |..? |..?
And I would want to pivot by name, to obtain
Name |Paperclip|Stapler|Desk Lamp|Block Notes
George| 1| 2| 1| NULL
Mark | 2| NULL | NULL | 1
I've follower the examples like
Convert Rows to columns using 'Pivot' in SQL Server
but I'm far from a solution.. can someone please give me an hand?
Thanks!
edit: the actual code
drop table #temp2
SELECT DISTINCT *,
CASE WHEN Item IS NULL THEN NULL ELSE COUNT(Item) OVER(PARTITION BY Name) END CNT
INTO #TEMP2
FROM [ISPBIGFIX].[dbo].[C_INV_ErroriTavolette_v11]
DECLARE #cols NVARCHAR (MAX)
DECLARE #Columns2 NVARCHAR (MAX)
SET #cols = SUBSTRING((SELECT DISTINCT ',['+Item+']' FROM #TEMP2 GROUP BY Item FOR XML PATH('')),2,8000)
SET #Columns2 = SUBSTRING((SELECT DISTINCT ',ISNULL(['+Item+'],0) AS ['+Item+']' FROM #TEMP2 GROUP BY Item FOR XML PATH('')),2,8000)
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT Name,' + #Columns2 + ' FROM
(
SELECT Name,ErrorType,CNT FROM #TEMP2
) x
PIVOT
(
SUM(CNT)
FOR [Item] IN (' + #cols + ')
) p
WHERE Name IS NOT NULL;'
EXEC SP_EXECUTESQL #query
Try this, It follows the same example mentioned here:Convert Rows to columns using 'Pivot' in SQL Server
--Drop Sample temp Table
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL
BEGIN
DROP TABLE #temp2
END
--create Sample temp Table
create Table #temp2
(
[name] varchar(255),
Item varchar(255),
note varchar(255)
)
--Insert Sample Data
insert into #temp2
values( 'George','Paperclip','Two boxes'),
('George','Stapler','blue one'),
('George','Stapler','red one'),
('George','Desk lamp','No light bulb'),
('Mark','Paperclip','One box 2'),
('Mark','Paperclip','One box 4'),
('Mark','Block Notes','a blue one')
DECLARE #cols AS NVARCHAR(MAX), #cols2 AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
--Generate Columns from Data
--Generate Columns from Data
select #cols = STUFF((SELECT ', isnull(' + QUOTENAME(Item) + ',0) as' + QUOTENAME(Item)
from #temp2
group by Item
order by Item
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols2 = STUFF((SELECT ', ' + QUOTENAME(Item)
from #temp2
group by Item
order by Item
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
--Pivot Query
set #query = 'SELECT [name],' + #cols + ' from
(
select [Name], Item, count(*) as xcount
from #temp2
group by Name, Item
) x
pivot
(
sum(xCount)
for Item in (' + #cols2+ ')
) p '
execute(#query);
--Drop Sample Temp Table
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL
BEGIN
DROP TABLE #temp2
END
Try This Dynamic Sql
IF OBJECT_ID('dbo.TT')IS NOT NULL
DROP TABLE TT
;WITH CTE(Name ,Item ,Note)
AS
(
SELECT 'George','Paperclip' ,'Two boxes' UNION ALL
SELECT 'George','Stapler' ,'blue one' UNION ALL
SELECT 'George','Stapler' ,'red one' UNION ALL
SELECT 'George','Desk lamp' ,'No light bulb' UNION ALL
SELECT 'Mark' ,'Paperclip' ,'One box 2' UNION ALL
SELECT 'Mark' ,'Paperclip' ,'One box 4' UNION ALL
SELECT 'Mark' ,'Block Notes','a blue one'
)
SELECT *,CASE WHEN Item IS NOT NULL THEN 1 ELSE 0 END AS Item2 INTO TT FROM CTE
SELECT * FROM TT
DECLARE #Sql nvarchar(max),
#Sqlcol nvarchar(max),
#ISNULLSqlcol nvarchar(max)
SELECT #Sqlcol=STUFF((SELECT DISTINCT ', '+QUOTENAME(Item)
FROM TT FOR XML PATH ('')),1,1,'')
SELECT #ISNULLSqlcol=STUFF((SELECT DISTINCT ', '+'ISNULL(SUM('+QUOTENAME(Item) +'),''0'') AS '+QUOTENAME(Item)
FROM TT FOR XML PATH ('')),1,1,'')
SET #Sql='SELECT Name,'+#ISNULLSqlcol+'FROM
(
SELECT * FROM TT
) AS SRc
PIVOT
(
SUM(Item2) FOR Item IN('+#Sqlcol+')
) AS Pvt GROUP BY Name'
Print #Sql
EXEC (#Sql)
Try this query (I think its much more clearly). I use the code of Kashif Qureshi to create a temporary table, but my code is different in PIVOT part
--Drop Sample temp Table
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL
BEGIN
DROP TABLE #temp2
END
--create Sample temp Table
create Table #temp2
(
[name] varchar(255),
Item varchar(255),
note varchar(255)
)
--Insert Sample Data
insert into #temp2
values( 'George','Paperclip','Two boxes'),
('George','Stapler','blue one'),
('George','Stapler','red one'),
('George','Desk lamp','No light bulb'),
('Mark','Paperclip','One box 2'),
('Mark','Paperclip','One box 4'),
('Mark','Block Notes','a blue one')
--- PIVOT
DECLARE #v_query VARCHAR(8000) -- main query
DECLARE #v_columns VARCHAR(8000) -- columns
SET #v_columns =''
-- Get string columns
SELECT #v_columns += '[' + CONVERT(VARCHAR, Item) +'],' FROM (SELECT DISTINCT Item FROM #temp2) AS temp
-- Delete the last comma
SET #v_columns = LEFT(#v_columns,LEN(#v_columns)-1)
-- Main query
SET #v_query = 'SELECT Name, ' + #v_columns +' FROM
(
SELECT Name, Item FROM #temp2
) T
PIVOT
(
Count(Item)
FOR Item IN ('+ #v_columns +')
) PVT'
EXEC (#v_query)
-- DROP
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL
BEGIN
DROP TABLE #temp2
END
The answer by Kashif works. Here is a version that leverages STRING_AGG
--Drop Sample temp Table
DROP TABLE IF EXISTS #temp2
--Create Sample temp Table
CREATE Table #temp2 (
[name] varchar(255),
Item varchar(255),
note varchar(255)
)
--Insert Sample Data
INSERT INTO #temp2
VALUES ( 'George','Paperclip','Two boxes'),
('George','Stapler','blue one'),
('George','Stapler','red one'),
('George','Desk lamp','No light bulb'),
('Mark','Paperclip','One box 2'),
('Mark','Paperclip','One box 4'),
('Mark','Block Notes','a blue one')
DECLARE #cols AS NVARCHAR(MAX), #cols2 AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
--Generate Columns from Data
SET #cols = (SELECT STRING_AGG (' isnull(' + QUOTENAME(Item) + ',0) as' + QUOTENAME(Item), ',') as colR
FROM (SELECT ITEM FROM (SELECT ITEM FROM #temp2 GROUP BY ITEM) x GROUP BY ITEM) Y )
SET #cols2 = (SELECT STRING_AGG (QUOTENAME(Item), ',') as colR
FROM (SELECT ITEM FROM (SELECT ITEM FROM #temp2 GROUP BY ITEM) x GROUP BY ITEM) Y )
SET #query = 'SELECT [name],' + #cols + '
FROM (
SELECT [Name], Item, COUNT(*) as xcount
FROM #temp2
GROUP BY Name, Item
) x
PIVOT (
SUM(xCount)
FOR Item IN (' + #cols2+ ')
) p '
EXECUTE (#query);
--Drop Sample Temp Table
DROP TABLE IF EXISTS #temp2

Query written to pull rows into columns without aggregation is jumbling the columns rather than listing them in order

TL;DR: Around 1800 functions are turned into columns for "pivot" but they are not being called in order, such that function_376 is consistently called first then "random" others and not all roles have that many functions and it's pulling nulls. How do I get it to pull the functions in order?
I am trying to create a query to produce a result set that can be easily copy and pasted into Excel in a human readable fashion. The normal result set from my query pulls two columns, role and function, with a row for each distinct pair. My objective is to pull the functions all onto the same line and have a column for each function assigned to the role. The closest I have gotten is to repurpose a script I found in this answer but the problem I am running into is that the query to return the results is jumbling the columns. It is not returning them in the order row, function1,function2, etc. and as result is pocked with NULLs which is making the output virtually useless. The #cols query is pulling the functions together in a consistent order every time I run it but it is not in numerical order, it appears random. Each Function_N column represents the Nth function associated with the role so if I could get the #cols query to build in order, then this would work.
How can I rewrite this so that the output will have the functions listed in numerical order such that the results are left justified?
Code and screenshot of results shown below.
IF OBJECT_ID('tempdb.dbo.#roles', 'U') IS NOT NULL
DROP TABLE #roles;
CREATE TABLE #roles([role] VARCHAR(MAX), [function] VARCHAR(MAX))
Insert into #roles
select distinct r.r_desc, f.f_desc
from roles r
join role_functions rf on rf_rid = r_id
join Functions f on f_id = rf_fid
where r.r_Active = 'y'
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ','
+ QUOTENAME(rn)
from
(
select 'function_'+cast(row_number() over(partition by [role]
order by [role]) as varchar(20)) rn
from #roles
) src
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [Role],' + #cols + ' from
(
select [Role], [function],
''function_''+cast(row_number() over(partition by [role]
order by [role]) as varchar(20)) rn
from #roles
) x
pivot
(
max([function])
for rn in (' + #cols + ')
) p '
execute(#query)
Image of results
I solved it! The distinct was a nod in the right direction. I also needed another temp table for calculating the max column count and then I used a variable to pass that into the first stuff function. See below:
IF OBJECT_ID('tempdb.dbo.#roles', 'U') IS NOT NULL
DROP TABLE #roles;
IF OBJECT_ID('tempdb.dbo.#funtcnt', 'U') IS NOT NULL
DROP TABLE #funtcnt;
CREATE TABLE #roles([role] VARCHAR(MAX), [function] VARCHAR(MAX))
Insert into #roles
select distinct r.r_desc, f.f_desc
from roles r
join role_functions rf on rf_rid = r_id
join Functions sf on f_id = rf_fid
where r.r_Active = 'y'
select [role], count([function]) as [count]
INTO #funtcnt
from #roles group by [role]
declare #topcount varchar(max) = (select top 1 [role] from #funtcnt order by [count] desc)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ','
+ QUOTENAME(rn)
from
(
select 'function_'+cast(row_number() over(partition by [role]
order by [role]) as varchar(20)) rn
from #roles where [role] = #topcount
) src
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [Role],' + #cols + ' from
(
select [Role], [function],
''function_''+cast(row_number() over(partition by [role]
order by [role]) as varchar(20)) rn
from #roles
) x
pivot
(
max([function])
for rn in (' + #cols + ')
) p '
execute(#query)

Dynamic pivot table with multiple columns in sql server

I am trying to pivot table DYNAMICALLY but couldn't get the desired result.
Here is the code to create a table
create table Report
(
deck char(3),
Jib_in float,
rev int,
rev_insight int,
jib_out float,
creation int
)
insert into Report values
('A_1',0.345,0,0,1.23,20140212),
('B_2',0.456,0,4,2.34,20140215),
('C_3',0.554,0,6,0.45,20140217),
('D_4',0.231,0,8,7.98,20140222),
('E_5',0.453,0,0,5.67,20140219),
('F_6',0.344,0,3,7.23,20140223)'
Code written so far.... this pivots the column deck and jib_in into rows but thats it only TWO ROWS i.e the one i put inside aggregate function under PIVOT function and one i put inside QUOTENAME()
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME(deck)
FROM (SELECT p.deck FROM dbo.report AS p
GROUP BY p.deck) AS x;
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT p.deck, p.jib_in
FROM dbo.report AS p
) AS j
PIVOT
(
SUM(jib_in) FOR deck IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT #sql;
EXEC sp_executesql #sql;
I need all the columns to be pivoted and show on the pivoted table. any help would be appreciated. I am very new at dynamic pivot. I tried so many ways to add other columns but no avail!!
I know there are other ways please feel free to mention if there is any other way to get this right.
Please use this (If you are getting Collation issue, please change all the 3 INT datatypes):
STATIC code:
SELECT HEADER, [A_1],[B_2],[C_3],[D_4],[E_5],[F_6]
FROM
(SELECT DECK,HEADER, VALUE FROM REPORT
UNPIVOT
(
VALUE FOR HEADER IN ([JIB_IN],[REV],[REV_INSIGHT],[JIB_OUT],[CREATION])
) UNPIV
) SRC
PIVOT
(
SUM(VALUE)
FOR DECK IN ([A_1],[B_2],[C_3],[D_4],[E_5],[F_6])
) PIV
Using Dynamic SQL:
DECLARE #COLSUNPIVOT AS NVARCHAR(MAX),
#QUERY AS NVARCHAR(MAX),
#COLSPIVOT AS NVARCHAR(MAX)
SELECT #COLSUNPIVOT = STUFF((SELECT ','+QUOTENAME(C.NAME)
FROM SYS.COLUMNS AS C
WHERE C.OBJECT_ID = OBJECT_ID('REPORT') AND C.NAME <> 'DECK'
FOR XML PATH('')), 1, 1, '')
SELECT #COLSPIVOT = STUFF((SELECT ',' + QUOTENAME(DECK)
FROM REPORT T FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
SET #QUERY
= 'SELECT HEADER, '+#COLSPIVOT+'
FROM
(
SELECT DECK,HEADER,VALUE FROM REPORT
UNPIVOT
(
VALUE FOR HEADER IN ('+#COLSUNPIVOT+')
) UNPIV
) SRC
PIVOT
(
SUM(VALUE)
FOR DECK IN ('+#COLSPIVOT+')
) PIV'
EXEC(#QUERY)

Flattening of a 1 row table into a key-value pair table

What's the best way to get a key-value pair result set that represents column-value in a row?
Given the following table A with only 1 row
Column1 Column2 Column3 ...
Value1 Value2 Value3
I want to query it and insert into another table B:
Key Value
Column1 Value1
Column2 Value2
Column3 Value3
A set of columns in table A is not known in advance.
NOTE: I was looking at FOR XML and PIVOT features as well as dynamic SQL to do something like this:
DECLARE #sql nvarchar(max)
SET #sql = (SELECT STUFF((SELECT ',' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY column_name FOR XML PATH('')), 1, 1, ''))
SET #sql = 'SELECT ' + #sql + ' FROM TableA'
EXEC(#sql)
A version where there is no dynamic involved. If you have column names that is invalid to use as element names in XML this will fail.
select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
T2.N.value('text()[1]', 'nvarchar(max)') as Value
from (select *
from TableA
for xml path(''), type) as T1(X)
cross apply T1.X.nodes('/*') as T2(N)
A working sample:
declare #T table
(
Column1 varchar(10),
Column2 varchar(10),
Column3 varchar(10)
)
insert into #T values('V1','V2','V3')
select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
T2.N.value('text()[1]', 'nvarchar(max)') as Value
from (select *
from #T
for xml path(''), type) as T1(X)
cross apply T1.X.nodes('/*') as T2(N)
Result:
Key Value
-------------------- -----
Column1 V1
Column2 V2
Column3 V3
Update
For a query with more than one table you could use for xml auto to get the table names in the XML. Note, if you use alias for table names in the query you will get the alias instead.
select X2.N.value('local-name(..)', 'nvarchar(128)') as TableName,
X2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
X2.N.value('text()[1]', 'nvarchar(max)') as Value
from (
-- Your query starts here
select T1.T1ID,
T1.T1Col,
T2.T2ID,
T2.T2Col
from T1
inner join T2
on T1.T1ID = T2.T1ID
-- Your query ends here
for xml auto, elements, type
) as X1(X)
cross apply X1.X.nodes('//*[text()]') as X2(N)
SQL Fiddle
I think you're halfway there. Just use UNPIVOT and dynamic SQL as Martin recommended:
CREATE TABLE TableA (
Code VARCHAR(10),
Name VARCHAR(10),
Details VARCHAR(10)
)
INSERT TableA VALUES ('Foo', 'Bar', 'Baz')
GO
DECLARE #sql nvarchar(max)
SET #sql = (SELECT STUFF((SELECT ',' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))
SET #sql = N'SELECT [Key], Val FROM (SELECT ' + #sql + ' FROM TableA) x '
+ 'UNPIVOT ( Val FOR [Key] IN (' + #sql + ')) AS unpiv'
EXEC (#sql)
Results:
Key Val
------------ ------------
Code Foo
Name Bar
Details Baz
There is a caveat, of course. All your columns will need to be the same data type for the above code to work. If they are not, you will get this error:
Msg 8167, Level 16, State 1, Line 1
The type of column "Col" conflicts with the type of
other columns specified in the UNPIVOT list.
In order to get around this, you'll need to create two column string statements. One to get the columns and one to cast them all as the data type for your Val column.
For multiple column types:
CREATE TABLE TableA (
Code INT,
Name VARCHAR(10),
Details VARCHAR(10)
)
INSERT TableA VALUES (1, 'Foo', 'Baf')
GO
DECLARE
#sql nvarchar(max),
#cols nvarchar(max),
#conv nvarchar(max)
SET #cols = (SELECT STUFF((SELECT ',' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))
SET #conv = (SELECT STUFF((SELECT ', CONVERT(VARCHAR(50), '
+ column_name + ') AS ' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name='TableA'
ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))
SET #sql = N'SELECT [Key], Val FROM (SELECT ' + #conv + ' FROM TableA) x '
+ 'UNPIVOT ( Val FOR [Key] IN (' + #cols + ')) AS unpiv'
EXEC (#sql)
Perhaps you're making this more complicated than it needs to be. Partly because I couldn't wrap my little brain around the number of PIVOT/UNPIVOT/whatever combinations and a dynamic SQL "sea of red" would be necessary to pull this off. Since you know the table has exactly one row, pulling the value for each column can just be a subquery as part of a set of UNIONed queries.
DECLARE #sql NVARCHAR(MAX) = N'INSERT dbo.B([Key], Value) '
SELECT #sql += CHAR(13) + CHAR(10)
+ ' SELECT [Key] = ''' + REPLACE(name, '''', '''''') + ''',
Value = (SELECT ' + QUOTENAME(name) + ' FROM dbo.A) UNION ALL'
FROM sys.columns
WHERE [object_id] = OBJECT_ID('dbo.A');
SET #sql = LEFT(#sql, LEN(#sql)-9) + ';';
PRINT #sql;
-- EXEC sp_executesql #sql;
Result (I only created 4 columns, but this would work for any number):
INSERT dbo.B([Key], Value)
SELECT [Key] = 'Column1',
Value = (SELECT [Column1] FROM dbo.A) UNION ALL
SELECT [Key] = 'Column2',
Value = (SELECT [Column2] FROM dbo.A) UNION ALL
SELECT [Key] = 'Column3',
Value = (SELECT [Column3] FROM dbo.A) UNION ALL
SELECT [Key] = 'Column4',
Value = (SELECT [Column4] FROM dbo.A);
The most efficient thing in the world? Likely not. But again, for a one-row table, and hopefully a one-off task, I think it will work just fine. Just watch out for column names that contain apostrophes, if you allow those things in your shop...
EDIT sorry, couldn't leave it that way. Now it will handle apostrophes in column names and other sub-optimal naming choices.