I'm trying to unpivot a table with a large number of columns in the format of:
PID UID col1 col2 col3...
The dynamic SQL below will get me almost everything except the name of the column. The goal is to fill in the "ID" field with the name of the column from which the unpivot value originated.
-- Build list of cols we want to unpivot (skip PID & UID)
declare #cols nvarchar(max)
select #cols = coalesce(#cols+N',', N'') + quotename(c.name) from syscolumns c
inner join sysobjects o on c.id = o.id and o.xtype = 'u'
where o.name = 'MyTable' and c.name not in ('PID', 'UID') order by c.colid
declare #query nvarchar(max)
select #query = N'
select PID, [UID], ID, Val
from
(
select PID, UID, ''ID'' as ID, ' + #cols + '
from MyTable
where UID <> 0
) as cp
unpivot
(
Val for Vals in (' + #cols + ')
) as up
'
exec sp_executesql #query
I thought maybe I could do some sort of join with syscolumns & MyTable and then do a second unpivot but I haven't been able to figure it out.
Ultimately my query should return
PID UID ID Val
123 456 'col1 name' 'xyz'
123 456 'col2 name' 'def'
123 333 'col1 name' 'fdf'
...
So while I know how to get the name of the columns in order to generate the dynamic SQL for the unpivot, I don't know how to join the name of the columns into the output of the unpivot.
You can reference the column name from the val for col in part of the unpivot. Col gets the column name
Example Fiddle
-- Build list of cols we want to unpivot (skip PID & UID)
declare #cols nvarchar(max)
select #cols = coalesce(#cols+N',', N'') + quotename(c.name) from syscolumns c
inner join sysobjects o on c.id = o.id and o.xtype = 'u'
where o.name = 'MyTable' and c.name not in ('PID', 'UID') order by c.colid
declare #query nvarchar(max)
select #query = N'
select PID, [UID], Col as ID, Val
from
(
select PID, UID, ' + #cols + '
from MyTable
where UID <> 0
) as cp
unpivot
(
Val for Col in (' + #cols + ')
) as up
'
exec sp_executesql #query
Related
I have a XML transposing rows into column query. I am trying to insert the data to a table that is created dynamically. However, I am getting error saying conversion failed when converting nvarchar value to data type int when I execute '#query'
This is my current code:
DECLARE #cols NVARCHAR(MAX), #cols1 NVARCHAR(MAX), #query NVARCHAR(MAX);
SET #cols = STUFF(
(
SELECT
','+QUOTENAME(c.[destfieldname] ) + c.datatype
FROM #specnorm c
left join #rawtemp d on c.fieldname = d.fieldname
group by c.destfieldname, c.datatype
order by min(sourceSequenceTypical)
for xml path ('')),1,1,'')+ ', [sequenceID] int, [subsequenceID] int';
SET #cols1 = STUFF(
(
SELECT
','+QUOTENAME(c.[destfieldname] )
FROM #specnorm c
left join #rawtemp d on c.fieldname = d.fieldname
group by c.destfieldname
order by min(sourceSequenceTypical)
for xml path ('')),1,1,'')+ ', [sequenceID],
[subsequenceID] ';
SET #query = ' SELECT '+#cols1+' from (SELECT
c.[rownumber],
c.[contents],
d.[destfieldname]
FROM #rawtemp c
left join #specnorm d on c.fieldname = d.fieldname
)x pivot (max(contents) for destfieldname in ('+#cols1+')) p'
exec ('CREATE Table dbo.sample' + ' (' +#cols + ')');
exec ('Insert into dbo.sample' + #query)
try:
SET #query = 'SELECT '+#cols+' into YOURTABLE from (SELECT
[rownumber],
[contents],
[fieldname]
FROM #rawtemp
) x pivot (max(contents) for fieldname in ('+#cols+')) p'
it creates YOURTABLE on the fly
I Have one CTE Query
WHICH Return This Output :-
I Have Another SQL Statement(With Pivot Query) Which has Following Output :-
I Want to Join Both Output in Single Query output.
I Have Tried to JOIN This Both Query. but, I can't.
I need to show TotalTraffic and UniqueAttendee(both column) output with Pivot table Output.
I want both output in one table format to show it.
my Code is below for both Output:
---output 1 Query(CTE):
;WITH Detailstbl AS (
SELECT
[S].[Title]
,COUNT([VAL].[UserID]) [TotalTraffic]
,COUNT(DISTINCT [VAL].[UserID]) [UniqueAttendee]
FROM [dbo].[AC_Session] [S]
INNER JOIN
[dbo].[AC_ViewActivityLogs] [VAL] ON [S].[SessionID] = [VAL].[ObjectID] AND [VAL].[ObjectName] = 'View Poster Session'
WHERE [S].[IsPosterType] = 1
GROUP BY [S].[Title]
)
SELECT * FROM Detailstbl;
---output 2 Query(Pivot):
DROP TABLE #tempTable
CREATE TABLE #tempTable
(
Title nvarchar(MAX),
TotalTraffic int,
viewdate nvarchar(50)
)
--inserthere
INSERT INTO #tempTable (Title,TotalTraffic,viewdate)
SELECT [S].[Title]
,COUNT([VAL].[UserID]) [TotalTraffic]
,CONVERT(VARCHAR(10), [ViewedOn], 101) AS [ViewedDate]
FROM [dbo].[AC_Session] [S]
INNER JOIN
[dbo].[AC_ViewActivityLogs] [VAL] ON [S].[SessionID] = [VAL].[ObjectID] AND [VAL].[ObjectName] = 'View Poster Session'
WHERE [S].[IsPosterType] = 1
GROUP BY [S].[Title],CONVERT(VARCHAR(10), [ViewedOn], 101)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.viewdate)
FROM #tempTable c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
PRINT #cols
set #query = '
SELECT * FROM (
SELECT Title,
' + #cols + ' from
(
select
Title,
TotalTraffic,
viewdate
from #tempTable
) x
pivot
(
max(TotalTraffic)
for viewdate in (' + #cols + ')
) p
) AS x '
execute(#query)
PRINT #query
Below what I have tried but getting error Incorrect syntax near ON
set #query = '
SELECT *
FROM Detailstbl
INNER JOIN(
SELECT * FROM (
SELECT Title,
' + #cols + ' from
(
select
Title,
TotalTraffic,
viewdate
from #tempTable
) x
pivot
(
max(TotalTraffic)
for viewdate in (' + #cols + ')
) p
) AS x ON Detailstbl.Title = x.Title
)'
execute(#query)
PRINT #query
The place where you written the On Clause is incorrect. it should be after the last ')' and you can't use the CTE inside this Dynamic SQL. better try to insert the CTE data into a TempTable Named #Detailstbl
Temp table
SELECT
[S].[Title]
,COUNT([VAL].[UserID]) [TotalTraffic]
,COUNT(DISTINCT [VAL].[UserID]) [UniqueAttendee]
INTO #Detailstbl
FROM [dbo].[AC_Session] [S]
INNER JOIN
[dbo].[AC_ViewActivityLogs] [VAL] ON [S].[SessionID] = [VAL].[ObjectID] AND [VAL].[ObjectName] = 'View Poster Session'
WHERE [S].[IsPosterType] = 1
GROUP BY [S].[Title]
Dynamic SQL
set #query = '
SELECT *
FROM #Detailstbl dtbl
INNER JOIN(
SELECT * FROM (
SELECT Title,
' + #cols + ' from
(
select
Title,
TotalTraffic,
viewdate
from #tempTable
) x
pivot
(
max(TotalTraffic)
for viewdate in (' + #cols + ')
) p
) dt
) x ON dtbl.Title = x.Title '
I need to Pivot Questions With Their Answers.The Desired Output of the pivot Query is below and the table structure too.
I have Created the SQL fiddle Here Sql Fiddle. I saw many examples of count with pivot but couldn't find something similar to this.
You simply need some joins to get the base data and a dynamic pivot to show the result in the desired format.
try the following:
drop table if exists #temp
--base table with all details
select s.ID SurveyID, s.SubmittedBy, sq.Question, sa.Answer
into #temp
from #Survey s
join #SurveyMaster sm on sm.ID = s.SurveyMasterID
join #SurveyQuestion sq on sq.SurveyMasterID = s.SurveyMasterID
join #SurveyAnswers sa on sa.SurveyQuestionID = sq.ID and sa.SurveyID = s.ID
--dynamic pivot
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.Question)
FROM #temp c
ORDER BY 1 DESC
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT SurveyID, SubmittedBy, ' + #cols + ' from
(
select SurveyID, SubmittedBy, Question, Answer
from #temp
) x
pivot
(
max(Answer)
for Question in (' + #cols + ')
) p '
--execution
execute(#query)
Please find the db<>fiddle here.
declare #sqlstring as varchar(max) = '';
SELECT
#sqlstring = STUFF((
SELECT distinct
',' + '[' + isnull(sq.Question, '''') + ']'
from
#surveyanswers sa
join
#SurveyQuestion sq
on sq.ID = sa.SurveyQuestionID
join
#survey sv
on sv.SurveyMasterID = sq.SurveyMasterID FOR XML PATH('')), 1, 1, '')
select
sa.SurveyID,
sv.SubmittedBy,
sq.Question,
sa.Answer into ##temp
from
#surveyanswers sa
join
#SurveyQuestion sq
on sq.ID = sa.SurveyQuestionID
join
#survey sv
on sv.SurveyMasterID = sq.SurveyMasterID
set
#sqlstring = 'SELECT SurveyID,SubmittedBy,' + #sqlstring + ' FROM (select * from ##temp ) t ' + ' PIVOT ( ' + ' max(Answer) FOR Question IN (' + #sqlstring + ' ) ) AS pivot_table' execute(#sqlstring);
RESULT:
In the table below, I have a variable number of columns, and that number is in the 1000s. I need to sum all the values of each of the 1000 columns grouped by the person's name. So, smith's total test_score_1, total test_score_2,...total test_score_1000. And then Jackson's total test_score_1, total test_score_2,...total test_score_1000.
I don't know the number of 'test_score_n' columns beforehand and they are always changing.
So given this table:
name test_score_1 test_score_2 ... test_score_1000
smith 2 1 0
jackson 0 3 1
jackson 1 1 2
jackson 3 0 3
smith 4 5 1
How can I produce the table below?
name test_score_1 test_score_2 ... test_score_1000
smith 6 6 1
jackson 4 4 6
SQL to generate the SQL
DECLARE #generatedSQL nvarchar(max);
SET #generatedSQL = (
SELECT
'SELECT ' +
SUBSTRING(X.foo, 2, 2000) +
'FROM ' +
QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) +
' GROUP BY name' --fix this line , edited
FROM
sys.tables t
CROSS APPLY
(
SELECT
', SUM(' + QUOTENAME(c.name) + ')'
FROM
sys.columns c
WHERE
c.object_id = t.object_id
AND
c.name <> 'Name'
FOR XML PATH('')
) X (foo)
WHERE
t.name = 'MyTable'
);
EXEC (#generatedSQL);
Demo: http://rextester.com/MAFCP19297
SQL
DECLARE #cols varchar(max), #sql varchar(max);
SELECT #cols =
COALESCE(#cols + ', ', '') + 'SUM(' + COLUMN_NAME + ') AS ' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = '<tbl name>'
AND COLUMN_NAME <> 'name'
-- The AND below may be optional - see "Additional Notes #1"
AND TABLE_CATALOG = '<database schema name>';
SET #sql = 'SELECT name, ' + #cols + ' FROM tbl GROUP BY name;';
EXEC (#sql);
Explanation
The DECLARE creates two variables - one for storing the column summing part of the SQL and the other for storing the whole dynamically created SQL statement to run.
The SELECT queries the INFORMATION_SCHEMA.COLUMNS system table to get the names of all the columns in tbl apart from the name column. (Alternatively the sys tables could be used - answers to this question discuss the relative merits of each). These row values are then converted into a single comma separated value using this method (which is arguably a little simpler than the alternative FOR XML PATH ('') method). The comma-separated values are a bit more than just the column names - they SUM over each column name and then assign the result with an alias of the same name.
The SET then builds a simple SQL statement that selects the name and all the summed values - e.g: SELECT name, SUM(test_score_1) AS test_score_1, SUM(test_score_2) AS test_score_2, SUM(test_score_1000) AS test_score_1000 FROM tbl GROUP BY name;.
The EXEC then runs the above query.
Additional Notes
If there is a possibility that the table name may not be unique across all databases then the following clause is needed in the select: AND TABLE_CATALOG = '<database schema name>'
My initial answer to this question was mistakenly using MySQL rather than SQL Server - this has now been corrected but the previous version is still in the edit history and might be helpful to someone...
Try this dynamic column generation Sql script
DECLARE #Sql nvarchar(max)
SET #Sql=( SELECT DISTINCT 'SELECT'+
STUFF((SELECT ', '+ ' SUM( '+ COLUMN_NAME +' ) AS '+ QUOTENAME( COLUMN_NAME )
FROM INFORMATION_SCHEMA.COLUMNS Where TABLE_NAME ='Tab1000'
FOR XML PATH (''),type).value('.','varchar(max)'),1,2,'')
+' From Tab1000'From INFORMATION_SCHEMA.COLUMNS Where TABLE_NAME ='Tab1000')
EXEC (#sql)
Try the below script
(set the #tableName= [yourTablename] and #nameColumn to the name of the field you want to group by)
Declare #tableName varchar(50)='totalscores'
Declare #nameColumn nvarchar(50)='name'
Declare #query as nvarchar(MAX) ;
select #query = 'select ' + nameColumn + cast(sumColumns as nvarchar(max)) + 'from ' + #tableName +' group by ' + nameColumn from (
select #nameColumn nameColumn, (SELECT
', SUM(' + QUOTENAME(c.name) + ') ' + QUOTENAME(c.name)
FROM
sys.columns c
WHERE
c.object_id=t.object_id and c.name != #nameColumn
order by c.name
FOR
XML path(''), type
) sumColumns
from sys.tables t where t.name= #tableName
)t
EXECUTE(#query)
Change tablename with your tablename.
Declare #query as nvarchar(MAX) = (SELECT
'SELECT name,' + SUBSTRING(tbl.col, 2, 2000) + ' FROM ' + QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) + 'Group By name'
FROM
sys.tables t
CROSS APPLY
(
SELECT
', SUM(' + QUOTENAME(columns.name) + ') as ' + columns.name
FROM
sys.columns columns
WHERE
columns.object_id = t.object_id and columns.name != 'name'
FOR XML PATH('')
) tbl (col)
WHERE
t.name = 'tablename')
select #query EXECUTE(#query)
GBN's dynamic SQL would be my first choice (+1), and would be more performant. However, if you are interested in breaking this horrible cycle of a 1,000+ columns, consider the following:
Example
Declare #YourTable Table ([col 1] int,[col 2] int,[col 1000] varchar(50))
Insert Into #YourTable Values
(2,1,0)
,(4,5,1)
Select Item = replace(C.Item,'_x0020_', ' ')
,Value = sum(C.Value)
From #YourTable A
Cross Apply (Select XMLData= cast((Select A.* for XML RAW) as xml)) B
Cross Apply (
Select Item = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','int')
From B.XMLData.nodes('/row') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
Where a.value('local-name(.)','varchar(100)') not in ('Fields','ToExclude')
) C
Group By C.Item
Returns
Item Value
col 1 6
col 2 6
col 1000 1
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.