Pivot a resultset (rows to columns) - sql

Consider the following #table:
guid field value
--------------------
123 A foo
123 B foobar
123 C 123
234 A bar;baz
234 B 3464
345 A foobaz
I need to transpose / pivot this into the following #table2:
guid A B C
---------------------------
123 foo foobar 123
234 bar;baz 3464 -
345 foobaz - -
In other words: the guid needs to stay the table key but all the fields need to be transposed into columns.
Is this possible in SQL Server?
Usually, I would use a server-side (php, python, asp) script to pull #table from the database and transpose the resultset by iterating to the resultset as an array but this is not an option in this case. I need a sql-only solution.
Any help would be greatly appreciated.

This should pivot your data and will keep your GUID.
select tbl.guid, tbl.[A], tbl.[B], tbl.[C] from (
select * from (
select guid, field, value
from #table
) t
pivot (
max(value) for field in ([A],[B],[C])
) p
) tbl
Creating your columns in preparation for the dynamic SQL:
declare #columns nvarchar(max) = (select stuff((
select distinct ',[' + t.field + ']'
from #table t
for xml path('')
),1,1,''))
Mixing the columns into the dynamic SQL:
declare #sql = N'
select tbl.guid, ' + #columns + ' from (
select * from (
select guid, field, value
from #table
) t
pivot (
max(value) for field in (' + #columns + ')
) p
) tbl'
And execute:
execute (#sql)

Related

Converting 200+ rows to column in SQL Server using PIVOT

I have done some research and so far, this one makes sense to me:
Convert row value in to column in SQL server (PIVOT)
However, I am wondering if there is any way to do this without having to declare the needed columns one by one, as I have more than 200 columns to retrieve.
Here's a sneak peek of what I have as a table:
ID | Col_Name | Col_Value
------------------------------
1 | Item_Num | 12345
2 | Item_Date | 34567
3 | Item_Name | 97454
4 | Item_App1 | 234567
5 | Item_App2 | 345678
Take note that I have 200+ distinct values for Col_Name and I want it to be Column fields.
I am using this one as of now but only for 5 columns:
SELECT * FROM
(
SELECT [ID], [Col_Name], [Col_Value]
FROM myTable
) AS a
PIVOT
(
MAX([Col_Value])
FOR [Col_Name] in ([Item_Num], [Item_Date], [Item_Name], [Item_App1], [Item_App2])
) AS p
ORDER BY [ID]
Is there any way this could be done considering the performance? Thanks in advance.
You can use dynamic sql to create the list of col_name:
declare #pivot_col varchar(max);
declare #sql varchar(max);
select #pivot_col = string_agg( cast(col_name as varchar(max)), ', ') within group ( order by col_name ) from ( select distinct col_name from tmp_table ) A;
set #sql = 'SELECT *
FROM (
SELECT [ID], [Col_Name], [Col_Value]
FROM tmp_table
) AS a
PIVOT
(
MAX([Col_Value])
FOR [Col_Name] in (' + #pivot_col + ' )
) AS p
ORDER BY [ID]';
exec ( #sql );
The PIVOT / UNPIVOT operators are built on the principles of an Entity Attribute Value model (EAV). The idea behind an EAV model is that you can extend database entities without performing database schema changes. For that reason an EAV model stores all attributes of an entity in one table as key/value pairs.
If that is the idea behind your design then use the dynamic sql query i posted above, otherwise use a regular record with 200+ columns for each id, as suggested by Gordon.
You can read about the performance of PIVOT / UNPIVOT operators here.
EDIT:
For sql server 2016 version:
declare #pivot_col varchar(max);
declare #sql varchar(max);
select #pivot_col = STUFF( (SELECT ',' + CAST(col_name AS VARCHAR(max)) AS [text()] FROM ( select distinct col_name from tmp_table ) A ORDER BY col_name FOR XML PATH('')), 1, 1, NULL);
set #sql = 'SELECT *
FROM ( SELECT [ID], [Col_Name], [Col_Value]
FROM tmp_table
) AS a
PIVOT
(
MAX([Col_Value])
FOR [Col_Name] in (' + #pivot_col + ' )
) AS p
ORDER BY [ID]';
exec ( #sql );

Group Columns based on Row ID

I have a table pulling data, like:
ID FID Value
001 20 200
001 20 400
001 50 600
002 50 100
How do write a query to get a column for each row ID that would sum the Value's?
For example, I want to return the following:
ID 20 50
001 600 600
002 NULL 100
A pattern like this:
SELECT
ID,
SUM(CASE WHEN FID = 20 THEN Value END) as sum20,
SUM(CASE WHEN FID = 50 THEN Value END) as sum50 --extend by adding more CASE WHEN rows
FROM
table
GROUP BY ID
..has the advantage of working in databases that don't support PIVOT syntax.
If you'd like PIVOT syntax:
SELECT ID, [20], [50] --extend by providing more values in square brackets
FROM
table
PIVOT
(
SUM(Value)
FOR FID IN ([20], [50]) --extend by providing more values in square brackets
) pvt
If you have dynamic list of FID's you can use dynamic query as below:
Declare #cols1 varchar(max)
Declare #query nvarchar(max)
Select #cols1 = stuff((select Distinct ','+QuoteName(Fid) from #data for xml path('')),1,1,'')
Set #query = ' Select * from (
Select Id, [Fid], [Value] from #data ) a
pivot (sum([Value]) for [Fid] in (' + #cols1 + ') ) p '
Exec sp_executesql #query

SQL: How do I get a comma delinated string separated into columns based on keyword?

I was able to find information on how to separate a comma delineated strings into columns here: How Do I Split a Delimited String in SQL Server Without Creating a Function? - but it doesn't quite solve my problem.
The data I am working with is a list of interests/activities for a given record. For instance, a record would have 'yoga, hiking' etc... At present we have 63 distinct activities total but we also need to accommodate for activities that do not yet exist.
What I've been tasked with is creating an automated process which:
Creates a new column in the table for each activity listed (and creates new columns as presently non-existent activities are added)
Have the columns reflect the name of the activity
If the record contains an activity, the column for that activity has a Y in it.
Ultimately, the outcome would look something like this...
id | activities | art | yoga | music | hiking | ...
-----------------------------------------------------------
1 | yoga, hiking | NULL | y | NULL | y | ...
2 | art, music, yoga | y | y | y | NULL | ...
Thank you in advance for any help that can be provided, let me know if any more information is required.
There are two concepts to learn here.
First, you have to split the string into rows. xquery is the fastest method on large recordsets, albeit more difficult to write.
Then you need to use a dynamic pivot to put the activities into new columns.
I've provided two outputs below, just pick which suites your needs best.
Xquery Example
Dynamic Pivot Example
Code sample:
if OBJECT_ID('tempdb..#tmp') is not null drop table #tmp
if OBJECT_ID('tempdb..#tmpsplit') is not null drop table #tmpsplit
declare #cols nvarchar(MAX), #sql nvarchar(MAX)
create table #tmp (id int identity(1,1), activities varchar(255))
insert into #tmp (activities)
values ('yoga, hiking'),
('art, music, yoga')
/* split activities into individual rows and store in #tmpsplit*/
SELECT DISTINCT A.ID,
ltrim(rtrim(Split.a.value('.', 'VARCHAR(max)'))) AS activity
into #tmpsplit
FROM (SELECT id,
CAST ('<M>' + REPLACE(CAST(activities AS VARCHAR), ',', '</M><M>') + '</M>' AS XML) AS String
FROM #tmp) AS A
CROSS APPLY String.nodes ('/M') AS Split(a)
WHERE LEN(Split.a.value('.', 'VARCHAR(max)'))>1
/* create list of activities to pivot */
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.activity)
FROM #tmpsplit c
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
/* create pivot: id and columns only*/
set #sql = 'select id, ' + #cols + '
from (select s.*, ''y'' as val
from #tmpsplit s) datatable
pivot (max(val) for activity in (' + #cols + ')
) p '
print(#sql)
exec(#sql)
/* create pivot: id, full activitites string, and columns*/
set #sql = 'select id, activities, ' + #cols + '
from (select s.*, t.activities, ''y'' as val
from #tmpsplit s
join #tmp t on s.id=t.id) datatable
pivot (max(val) for activity in (' + #cols + ')
) p '
print(#sql)
exec(#sql)

pivoting rows to columns in tsql

I have the following table with the following sample data
ID Language Question SubQuestion SubSubQuestion TotalCount TotalPercent
3 E 9 0 1 88527 73%
3 E 9 0 2 19684 16%
3 E 9 0 3 12960 11%
3 E 9 0 9 933 1%
I want all in one row like this
ID Language TotalCount901 TotalPercent901 TotalCount902 TotalPercent902 TotalCount903 TotalPercent903
3 E 88527 73% 19684 16% 12960 11%
I've tired using the pivot command, but it dosnt to work for me.
I made a few assumptions based on your column names, but it looks like you want to use something similar to this. This applies both an UNPIVOT and then a PIVOT to get the values in the columns you requested:
select *
from
(
select id,
language,
col + cast(QUESTION as varchar(10))
+cast(subquestion as varchar(10))
+cast(SubSubQuestion as varchar(10)) col,
value
from
(
select id, language,
cast(TotalCount as varchar(10)) TotalCount,
totalPercent,
question, subquestion, SubSubQuestion
from yourtable
) usrc
unpivot
(
value
for col in (totalcount, totalpercent)
) un
) srcpiv
pivot
(
max(value)
for col in ([TotalCount901], [totalPercent901],
[TotalCount902], [totalPercent902],
[TotalCount903], [totalPercent903],
[TotalCount909], [totalPercent909])
) p
See SQL Fiddle with Demo
Note: when performing the UNPIVOT the columns need to be of the same datatype. If they are not, then you will need to convert/cast to get the datatypes the same.
If you have an unknown number of values to transform, you can use dynamic sql:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsPivot
= STUFF((SELECT ','
+ QUOTENAME(c.name +
cast(QUESTION as varchar(10))
+cast(subquestion as varchar(10))
+cast(SubSubQuestion as varchar(10)))
from yourtable t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name in ('TotalCount', 'TotalPercent')
group by c.name, t.question, t.subquestion, t.subsubquestion
order by t.SubSubQuestion
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'select *
from
(
select id,
language,
col + cast(QUESTION as varchar(10))
+cast(subquestion as varchar(10))
+cast(SubSubQuestion as varchar(10)) col,
value
from
(
select id, language,
cast(TotalCount as varchar(10)) TotalCount,
totalPercent,
question, subquestion, SubSubQuestion
from yourtable
) usrc
unpivot
(
value
for col in (totalcount, totalpercent)
) un
) srcpiv
pivot
(
max(value)
for col in (' + #colsPivot + ')
) p '
execute(#query)
See SQL Fiddle with Demo

How do i transform rows into columns in sql server 2005

There is a question here in stackoverflow with the same title but that is not what I am looking for.
I have a table like the one below
Name | Count
----------------
Chery | 257
Drew | 1500
Morgon | 13
Kath | 500
Kirk | 200
Matt | 76
I need to trasform this result set into something like this
Chery | Drew | Morgon | Kath | Kirk | Matt
-------------------------------------------
257 1500 13 500 200 76
How do i acheive this using sql server 2005?
There are similar questions here,here answered in stackoverflow.
You need to use the operator PIVOT in your query to acheive this.Here is the example and explanation on how you can do that.The example is referenced from this source.
---I assumed your tablename as TESTTABLE---
DECLARE #cols NVARCHAR(2000)
DECLARE #query NVARCHAR(4000)
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + t.Name
FROM TESTTABLE AS t
ORDER BY '],[' + t.Name
FOR XML PATH('')
), 1, 2, '') + ']'
SET #query = N'SELECT '+ #cols +' FROM
(SELECT t1.Name , t1.Count FROM TESTTABLE AS t1) p
PIVOT (MAX([Count]) FOR Name IN ( '+ #cols +' ))
AS pvt;'
EXECUTE(#query)
Explanation
1.The first part of the query
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + t.Name
FROM TESTTABLE AS t
ORDER BY '],[' + t.Name
FOR XML PATH('')
), 1, 2, '') + ']'
gives you a nice flattened result of your Name column values in a single row as follow
[Cheryl],[Drew],[Karen],[Kath],[Kirk],[Matt]
You can learn more about the STUFF and XML PATH here and here.
2.SELECT + #cols + FROM will select all the rows as coloumn names for the final result set (pvt - step 3)
i.e
Select [Chery],[Drew],[Morgan],[Kath],[Kirk],[Matt]
3.This query pulls all the rows of data that we need to create the cross-tab results. The (p) after the query is creating a temporary table of the results that can then be used to satisfy the query for step 1.
(SELECT t1.Name, t1.Count FROM TESTTABLE AS t1) p
4.The PIVOT expression
PIVOT (MAX (Count) FOR Name IN ( #cols) AS pvt
does the actual summarization and puts the results into a temporary table called pvt as
Chery | Drew | Morgon | Kath | Kirk | Matt
-------------------------------------------
257 1500 13 500 200 76
See Using PIVOT and UNPIVOT.
You can use the PIVOT and UNPIVOT
relational operators to change a
table-valued expression into another
table. PIVOT rotates a table-valued
expression by turning the unique
values from one column in the
expression into multiple columns in
the output, and performs aggregations
where they are required on any
remaining column values that are
wanted in the final output. UNPIVOT
performs the opposite operation to
PIVOT by rotating columns of a
table-valued expression into column
values.
The quick answer is
SELECT Chery, Drew, Morgon, Kath, Kirk, Matt
FROM
(SELECT [Name], [Count] From Foo)
PIVOT
(
MIN([Count])
FOR [Name] IN (Chery, Drew, Morgon, Kath, Kirk, Matt)
) AS PivotTable
If you want to avoid anything complicated like a pivot or the accepted answer, you can do this! (most of the code is just setting up the test data just in case anyone wants to try it)
/* set up your test table */
declare #TestData table (Name Varchar(80),[Count] int)
insert into #TestData (Name, [count])
Select 'Chery' as name, 257 as [count]
union all select 'Drew', 1500
union all select 'Morgon',13
union all select 'Kath', 500
union all select 'Kirk', 200
union all select 'Matt', 76
/* the query */
Declare #Query Varchar(max)
Select #Query=Coalesce(#query+', ','SELECT ') +Convert(VarChar(5),[count]) +' as ['+name+']'
from #TestData
Execute (#Query)
/* result
Chery Drew Morgon Kath Kirk Matt
----------- ----------- ----------- ----------- ----------- -----------
257 1500 13 500 200 76
*/