Merge two or more columns dynamically based on table columns? - sql

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 |

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?

SQL group rows to columns [duplicate]

This question already has answers here:
Simple way to transpose columns and rows in SQL?
(9 answers)
Closed 6 years ago.
I have a table
ID | Customer | Type | Value |
---+----------+---------+-------+
1 | John | Income | 50 |
2 | John | Income | 20 |
3 | Mike | Outcome | 150 |
4 | Robert | Income | 100 |
5 | John | Outcome | 300 |
Want a table like that;
| John | Mike | Robert |
--------+------+------+--------+
Income | 70 | 0 | 100 |
Outcome| 300 | 150 | 0 |
What should be the SQL Query? Thanks
The problem is Customers and Type are not static they are dynamic.
What I tried:
SELECT 'TotalIncome' AS TotalSalaryByDept,
[John], [Mike]
FROM
(SELECT Customer, Income
FROM table001) AS a
PIVOT
(
SUM(Income)
FOR ID IN ([John], [Mike])
) AS b;
Here is a quick dynamic pivot. We use a CROSS APPLY to unpivot the desired measures.
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (
Select Item=A.Customer,B.*
From YourTable A
Cross Apply (
Select Type=''Income'' ,Value=A.Income Union All
Select Type=''Outcome'',Value=A.Outcome
) B
) A
Pivot (sum(value) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
EDIT - For the Revised Question
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (Select Customer,Type,Value from YourTable ) A
Pivot (Sum(Value) For [Customer] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
The Table as you have it is how it should be in your SQL database. Columns are reserved for classifying your data, and rows are where you add new instances.
What you need to do is set up your ASP, Excel Pivot Table, or whatever you are using to display the data to format it into a horizontal table. I would need to know what you are using to interface with your database to give you an example.

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

SQL Server Pivot for counting instances in join table

I have 3 tables; category, location and business.
The category and location tables simply have an id, and a name.
Each business record has a categoryID, and a locationID, and a name field.
I'd like to construct a table that shows as a matrix, the number of businesses in each location and category combination. So having the categories as columns and locations as rows, with the counts in as the cell data.
Having a totals column and row would also be amazing.
I know I should be able to do this with pivot tables but I'm unable to get my head around the syntax for the pivots.
Any help would be much appreciated.
Thanks,
Nick
Edit: Here is a JS fiddle of my tables; http://sqlfiddle.com/#!2/4d6d2/1
Desired output:
| Activities | Bars | Sweet shops | Total
Chester | 1 | 0 | 0 | 1
Frodsham | 0 | 2 | 0 | 2
Stockport | 1 | 0 | 1 | 2
Total | 2 | 2 | 1 | 5
To get the final result that you want you can use the PIVOT function. I would first start with a subquery that returns all of your data plus gives you a total of each activity per location:
select l.name location,
c.name category,
count(b.locationid) over(partition by b.locationid) total
from location l
left join business b
on l.id = b.locationid
left join category c
on b.categoryid = c.id;
See SQL Fiddle with Demo. Using the windowing function count() over() creates the total number of activities for each location. Once you have this, then you can pivot the data to convert your categories to columns:
select
isnull(location, 'Total') Location,
sum([Activities]) Activities,
sum([Bars]) bars,
sum([Sweet Shops]) SweetShops,
sum(tot) total
from
(
select l.name location,
c.name category,
count(b.locationid) over(partition by b.locationid) tot
from location l
left join business b
on l.id = b.locationid
left join category c
on b.categoryid = c.id
) d
pivot
(
count(category)
for category in ([Activities], [Bars], [Sweet Shops])
) piv
group by grouping sets(location, ());
See SQL Fiddle with Demo. I also implemented GROUPING SETS() to create the final row with the totals for each activity.
The above works great if you have a limited number of activities but if your activities will be unknown, then you will want to use dynamic SQL:
DECLARE
#cols AS NVARCHAR(MAX),
#colsgroup AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(name)
from dbo.category
group by id, name
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsgroup = STUFF((SELECT ', sum(' + QUOTENAME(name)+ ') as '+ QUOTENAME(name)
from dbo.category
group by id, name
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT
Isnull(location, ''Total'') Location, '+ #colsgroup + ', sum(Total) as Total
from
(
select l.name location,
c.name category,
count(b.locationid) over(partition by b.locationid) total
from location l
left join business b
on l.id = b.locationid
left join category c
on b.categoryid = c.id
) x
pivot
(
count(category)
for category in ('+#cols+')
) p
group by grouping sets(location, ());'
exec sp_executesql #query;
See SQL Fiddle with Demo. Both versions give the result:
| LOCATION | ACTIVITIES | BARS | SWEET SHOPS | TOTAL |
|-----------|------------|------|-------------|-------|
| Chester | 1 | 0 | 0 | 1 |
| Frodsham | 0 | 1 | 0 | 1 |
| Stockport | 1 | 0 | 1 | 2 |
| Total | 2 | 1 | 1 | 4 |
`SELECT b.businessName, count(l.locationId),count(b.categoryId)
FROM businesses b JOIN locations l ON b.locationId=l.locationId
JOIN categories c ON b.categoryId=c.categoryId GROUP BY b.businessName;`

PIVOTing variable number of rows to columns

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) |