PIVOTing variable number of rows to columns - sql

I'm currently attempting to PIVOT some rows to columns. The problem is that I don't always know how many rows will be available. Let's look at an example:
Values_Table Columns_Table
------------ -----------
ID ID
ColumnsTableID GroupID
Value ColumnName
RESULTS"
Columns_Table
---------------
ID | GroupID | ColumnName
---------------------------------
0 1 Cats
1 1 Dogs
2 1 Birds
3 2 Pontiac
4 2 Ford
5 3 Trex
6 3 Raptor
7 3 Triceratops
8 3 Kentrosaurus
SQL FIDDLE EXAMPLE of a STATIC pivot. I am trying to achieve a dynamic pivot - http://sqlfiddle.com/#!3/2be82/1
So, here is my dilemma: I want to be able to pivot an unknown number of columns based on, in this scenario, the GroupID.
I want to be able to PIVOT, for example, all the rows in GroupID 3 into columns. I would need to do this without knowing how many rows are in groupID 3.
The design of the database is set in stone, so I can't do anything about that. All I can do is work with what I have :(
So, that said- does anyone have any suggestions on how to accomplish this task of PIVOTing an unknown number of rows into columns based on, in this example, the groupID?

If you are not going to know the values ahead of time, then you will need to look at using dynamic SQL. This will create a SQL String that will be executed, this is required because the list of columns must be known when the query is run.
The code will be similar to:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#groupid as int
set #groupid = 3
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(GroupName)
from Columns_Table
where groupid = #groupid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
SELECT B.GroupName, A.Value
, row_number() over(partition by a.ColumnsTableID
order by a.Value) seq
FROM Values_Table AS A
INNER JOIN Columns_Table AS B
ON A.ColumnsTableID = B.ID
where b.groupid = '+cast(#groupid as varchar(10))+'
) p
pivot
(
min(P.Value)
for P.GroupName in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. For groupid of 3, then the result will be:
| KENTROSAURUS | RAPTOR | TREX | TRICERATOPS |
| whatisthiseven | Itsaraptor | Jurassic | landbeforetime |
| (null) | zomg | Park | (null) |

Related

SQL count and group then pivot

So, I have been having this problem and I guess I am just too overloaded to figure it out. I have a database that I need to count from. That's all good. But where I run into a problem is i need to store it as only 2 rows, one for all the dates and one for the count. Here is an example:
obj_name | date_made
--------------------
1 | 2016-3-04
2 | 2016-5-23
3 | 2016-5-23
4 | 2016-5-23
5 | 2016-6-07
6 | 2016-6-07
7 | 2016-6-07
8 | 2016-6-07
9 | 2016-9-12
10 | 2016-9-12
What I want is to count how many objects are created on a certain date, then return it as 2 rows - one with all the dates then one with all the counts
Row1 | 2016-3-04 | 2016-5-23 | 2016-6-07 | 2016-9-12
Row2 | 1 | 3 | 4 | 2
If anyone can help that would be much appreciated.
here is what I have so far, I can get all the info I need but as 2 columns and I need it as 2 rows
SELECT datem,
SUM(num) AS total_num
FROM (
SELECT date_made AS datem,
obj_name,
COUNT(1) AS num
FROM db.tn
GROUP BY 1,2
) sub
GROUP BY 1
ORDER BY 1 DESC
You can try a dynamic pivot query like below
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(date_made)
FROM tbl
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #query =
'SELECT * FROM '+
'(SELECT COUNT(1) count, date_made FROM tbl ) src '+
' pivot '+
'( max(count) for date_made in ('+#cols+'))p'
EXEC(#query)
If I read this correctly, you are going to end up with an aggregate table that looks like this:
date_made | count
----------|------
2016-3-04 | 1
2016-5-23 | 3
2016-6-07 | 4
2016-9-12 | 2
And then you want to pivot the table on its side to look like the output in your initial question. Therefore, I think this is a repeat of this question:
Simple way to transpose columns and rows in Sql?

Count number of values across multiple columns

I have a table with 11 columns. The first column includes the category names. The remaining 10 columns have values like white, green, big, damaged etc. and these values can change in time.
I need a SQL query to find how many are there in table (in 10 columns) each value.
Table 1:
+------------+------------+
| ID | decription |
+------------+------------+
| 1 | white |
| 2 | green |
| 3 | big |
| 4 | damaged |
+------------+------------+
Table 2:
+------------+-----------+-----------+-----------+
| CATEGORY | SECTION 1 | SECTION 2 | SECTION 3 |
+------------+-----------+-----------+-----------+
| Category 1 | white | green | big |
| Category 2 | big | damaged | white |
| Category 1 | white | green | big |
| Category 3 | big | damaged | white |
+------------+-----------+-----------+-----------+
Desired result:
+------------+-------+-------+-----+---------+
| CATEGORY | White | Green | Big | Damaged |
+------------+-------+-------+-----+---------+
| Category 1 | 20 | 10 | 9 | 50 |
| Category 2 | 25 | 21 | 15 | 5 |
+------------+-------+-------+-----+---------+
Is it possible doing like this dynamically just as query ?
its on MS sql in visual studio reporting
Thanks
You've got yourself a bit of a mess with the design and the desired result. The problem is that your table is denormalized and then the final result you want is also denormalized. You can get the final result by unpivoting your Section columns, then pivoting the values of those columns. You further add to the mess by needing to do this dynamically.
First, I'd advise you to rethink your table structure because this is far too messy to maintain.
In the meantime, before you even think about writing a dynamic version to get the result you have to get the logic correct via a static or hard-coded query. Now, you didn't state which version of SQL Server you are using but you first need to unpivot the Section columns. You can use either the UNPIVOT function or CROSS APPLY. Your query will start with something similar to the following:
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
See SQL Fiddle with Demo. This gets your data into the format:
| CATEGORY | VALUE |
|------------|---------|
| Category 1 | white |
| Category 1 | green |
| Category 1 | big |
| Category 2 | big |
| Category 2 | damaged |
| Category 2 | white |
Now you have multiple Category rows - one for each value that previously were in the Section columns. Since you want a total count of each word in the Category, you can now apply the pivot function:
select
category,
white, green, big, damaged
from
(
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
) un
pivot
(
count(value)
for value in (white, green, big, damaged)
) p;
See SQL Fiddle with Demo. This will give you the result that you want but now you need this to be done dynamically. You'll have to use dynamic SQL which will create a SQL string that will be executed giving you the final result.
If the number of columns to UNPIVOT is limited, then you will create a list of the new column values in a string and then execute it similar to:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from yourtable
cross apply
(
select Section1 union all
select Section2 union all
select Section3
) d (SectionValue)
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo
If you have an unknown number of columns to unpivot, then your process will be a bit more complicated. You'll need to generate a string with the columns to unpivot, you can use the sys.columns table to get this list:
select #colsUnpivot
= stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Section%'
for xml path('')), 1, 1, '')
Then you'll need to get a list of the new column values - but since these are dynamic we will need to generate this list with a bit of work. You'll need to unpivot the table to generate the list of values into a temporary table for use. Create a temp table to store the values:
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
Load the temp table with the data that you need to unpivot:
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u'
insert into #Category_Section exec(#unpivotquery);
See SQL Fiddle with Demo. You'll see that your data looks the same as the static version above. Now you need to create a string with the values from the temp table that will be used in the final query:
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
Once you have all this you can put it together into a final query:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#unpivotquery AS NVARCHAR(MAX);
select #colsUnpivot
= stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Section%'
for xml path('')), 1, 1, '');
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u';
insert into #Category_Section exec(#unpivotquery);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in ('+ #colsunpivot +')
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo. All versions will get you the end result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
If your values are stored in a separate table, then you would generate your list of values from that table:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(decription)
from descriptions
group by decription
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo and still get the same result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
select category,
SUM(CASE when section1='white' then 1 when section2='white' then 1 when section3='white' then 1 else 0 end) as white,
SUM(CASE when section1='green' then 1 when section2='green' then 1 when section3='green' then 1 else 0 end) as green,
SUM(CASE when section1='damaged' then 1 when section2='damaged' then 1 when section3='damaged' then 1 else 0 end) as damaged,
SUM(CASE when section1='big' then 1 when section2='big' then 1 when section3='big' then 1 else 0 end) as big
from test
group by category
SQLFiddle
You can extend more to n section values as shown above gor section1,section2,section3

Pivot Table issue: no identifier in original table or enumeration

I'm having an issue with using pivot or dynamic tables.
I found this example to be very helpful in understanding how I can accomplish this task; however, I am missing the inside identifier column.
Dynamic Pivot Columns in SQL Server
There is an SQL fiddle provided here in the post above: http://www.sqlfiddle.com/#!3/7fad2/6
You can see that in the second table propertyObjects, there is a count 1, 2, 3, 4 for each objectID. I do not have that propertyID count. This is all I have
case category
1 xx
1 xyx
1 abc
2 ghj
2 asdf
3 dfgh
As you can see I have a number of different categories for each case, but no category identifier field.
This is what I need:
case cat1 cat2 cat3
1 xx xyx abc
2 ghj asdf
3 dfgh
So I am thinking I might need to add a column to the source table and somehow enumerate the categories per case. This would make it possible for me to use the pivot in the provided example. Thoughts?
I tried to use row_number to accomplish this, but it does not stop at each case number, it just continues on counting the entire table.
Since you have multiple values for each case, then you will need to use row_number() to get the separate columns for each category.
Before you write the dynamic SQL version I would first write a hard-coded version. The code will be similar to:
SELECT [case], cat1, cat2, cat3
FROM
(
SELECT [case], category,
'cat'+
cast(row_number() over(partition by [case]
order by category) as varchar(10)) seq
FROM yourTable
) x
PIVOT
(
max(category)
for seq in (cat1, cat2, cat3)
)p;
See SQL Fiddle with Demo.
Now you have the logic down, then you can convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('cat'+cast(seq as varchar(10)))
from
(
select row_number() over(partition by [case]
order by category) seq
from yourtable
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [case],' + #cols + '
from
(
SELECT [case], category,
''cat''+
cast(row_number() over(partition by [case]
order by category) as varchar(10)) seq
FROM yourTable
) x
pivot
(
max(category)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. This will give you the result:
| CASE | CAT1 | CAT2 | CAT3 |
|------|------|--------|--------|
| 1 | abc | xx | xyx |
| 2 | asdf | ghj | (null) |
| 3 | dfgh | (null) | (null) |

Merge two or more columns dynamically based on table columns?

I have a table which is called SectionNames as follows
SectionID SectionCode Subsection
1 xYz Individual
2 xYz Family
3 CYD Friends
4 PCPO level1
5 PCPO level2
6 PCPO level3
So on. So in future we can add one or more subsections for each section code.
And have one more table which is a reference table for above SectionNames table and Employee table with employee data.
ID EmployeeID SectionID Cost
1 1 1 $200
2 1 2 $300
3 1 3 $40
4 1 4 $10
5 1 5 No Level
6 1 6 No Level
7 1 7 $20
8 1 8 No Level
9 1 9 No Level
So Iwant the out put from these tables should look like:
EmployeeID Individual_xyz_Cost Family_xyz_Cost Friends_xyz_cost level1_PCPO_cost level2_PCPO_Cost
1 $200 $300 $400 $10 NoLevel
There are few employee records exists in my employee table. And I want this to be dynamic. Like If in future if one more subsection called Relatives added for XYZ section then my query should return Relatives_XYZ_Cost.
How can I write this query dynamically?
You will want to use the PIVOT function to transform the data from columns into rows. If you are going to have an unknown number of values that need to be columns, then you will need to use dynamic SQL.
It is easier to see a static or hard-coded version first and then convert it into a dynamic SQL version. A static version is used when you have a known number of values:
select *
from
(
select e.employeeid,
s.subsection +'_'+s.sectioncode+'_Cost' Section,
e.cost
from employee e
inner join sectionnames s
on e.sectionid = s.sectionid
) src
pivot
(
max(cost)
for section in (Individual_xYz_Cost, Family_xYz_Cost,
Friends_CYD_Cost, level1_PCPO_Cost,
level2_PCPO_Cost, level3_PCPO_Cost)
) piv;
See SQL Fiddle with Demo.
If you need the query to be flexible, then you will convert this to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(subsection +'_'+sectioncode+'_Cost')
from SectionNames
group by subsection, sectioncode, sectionid
order by sectionid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT employeeid,' + #cols + '
from
(
select e.employeeid,
s.subsection +''_''+s.sectioncode+''_Cost'' Section,
e.cost
from employee e
inner join sectionnames s
on e.sectionid = s.sectionid
) x
pivot
(
max(cost)
for section in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result of both is:
| EMPLOYEEID | INDIVIDUAL_XYZ_COST | FAMILY_XYZ_COST | FRIENDS_CYD_COST | LEVEL1_PCPO_COST | LEVEL2_PCPO_COST | LEVEL3_PCPO_COST |
----------------------------------------------------------------------------------------------------------------------------------
| 1 | $200 | $300 | $40 | $10 | No Level | No Level |

Flatten variable length DB rows into a single column

I've searched and can't find a solution to this that exactly fits my needs, nor can I find one that I can modify. I have a database table, for simplicity we'll say it has three columns (packageID, carrier, and sequence). For any package there can be one or more carriers that have handled the package. I can do a query like
SELECT packageID, carrier
FROM packageFlow
ORDER BY sequence
to get a list of all the people that have handled packages that looks like:
packageID, carrier
1, Bob
1, Jim
1, Sally
1, Ron
2, Reggie
2, Mary
2, Bruce
What I need though is to get the results into rows that look like:
packageID|carrier1|carrier2|carrier3|carrier4
1 |Bob |Jim |Sally |Ron
2 |Reggie |Mary |Bruce
Pivot doesn't seem to do what I need since I'm not aggregating anything and I can't get a CTE to work correctly either. I'd appreciate any nudges in the right direction.
This data transformation is a PIVOT. Starting in SQL Server 2005, there is a function that will convert the rows into columns.
If you have a known number of values, then you can hard-code your query:
select *
from
(
select packageid, carrier,
'Carrier_'+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageflow
) src
pivot
(
max(carrier)
for col in (Carrier_1, Carrier_2, Carrier_3, Carrier_4)
) piv
See SQL Fiddle with Demo.
If you have an unknown number of Carrier values that you want to turn into columns, then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(t.col)
from
(
select 'Carrier_'+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageFlow
) t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT packageid,' + #cols + ' from
(
select packageid, carrier,
''Carrier_''+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageflow
) x
pivot
(
max(carrier)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
Note: you will replace the order by packageid with order by sequence
The result of both queries is:
| PACKAGEID | CARRIER_1 | CARRIER_2 | CARRIER_3 | CARRIER_4 |
-------------------------------------------------------------
| 1 | Bob | Jim | Sally | Ron |
| 2 | Reggie | Mary | Bruce | (null) |