SQL Server (2012): Pivot (or Group by?) on Multiple Columns - sql

I have the following table:
month seating_id dept_id
Jan 1 5
Jan 8 9
Jan 5 3
Jan 7 2
Jan 1 5
Feb 1 9
Feb 8 9
Feb 5 3
Feb 7 2
Feb 7 1
I want to count each type of seating_id and dept_id for each month, so the result would look something like this:
month seating_id_1 seating_id_5 seating_id_7 seating_id_8 dept_id_1 dept_id_2 dept_id_3 dept_id_5 dept_id_9
Jan 2 1 1 1 0 1 1 2 1
Feb 0 1 2 1 1 1 1 0 2
I've experimented with unpivot/pivot and GROUP BY but haven't been able to achieve the desired results. Note, I would like to perform this as a SELECT statement, and not PROCEDURE call, if possible.
Let me know if you need any other info.

In case you need to go Dynamic
Example
Declare #SQL varchar(max) = '
Select *
From (
Select month,B.*
From YourTable A
Cross Apply (values (concat(''seating_id_'',seating_id),seating_id)
,(concat(''dept_id_'',dept_id),dept_id)
) b(item,value)
) A
Pivot (count([Value]) For [Item] in (' + Stuff((Select Distinct concat(',[seating_id_',seating_id,']') from YourTable For XML Path('')),1,1,'')
+','+
Stuff((Select Distinct concat(',[dept_id_',dept_id,']') from YourTable For XML Path('')),1,1,'')
+ ') ) p'
Exec(#SQL);
Returns
The Generated SQL Looks like this
Select *
From (
Select month,B.*
From YourTable A
Cross Apply (values (concat('seating_id_',seating_id),seating_id)
,(concat('dept_id_',dept_id),dept_id)
) b(item,value)
) A
Pivot (count([Value]) For [Item] in ([seating_id_1],[seating_id_5],[seating_id_7],[seating_id_8],[dept_id_1],[dept_id_2],[dept_id_3],[dept_id_5],[dept_id_9]) ) p

i have an answer you might not like but it does it:
select month
, sum(case seating_id when 1 then 1 else 0 end) as seating_id_1
, sum(case seating_id when 2 then 1 else 0 end) as seating_id_2
...
, sum(case dept_id when 1 then 1 else 0 end) as dept_id_1
, sum(case dept_id when 2 then 1 else 0 end) as dept_id_2
...
from YourTable
group by month

Related

pivot cumulative total by year, then selecting top 5

I am trying to get some pivot some year data to give me a cumulative total as the years increase and then get the top 5.
I have tried using a SUM on the total, for the years in the year column, but it doesn't appear to be increasing. The issue I think is due to some null values potentially?
The data in the table currently appears like
Name | ApplesEaten | Year
Bob | 2 | 2012
Bob | 5 | 2016
Elvis| 1 | 2017
Elvis| 2 | 2012
Sam | 8 | 2008
Elvis| 6 | 2004
Sam | 24 | 2019
Sarah| 14 | 2015
Bob | 6 | 2005
Rachel| 12 | 2010
Rachel| 10 | 2008
Bob | 82 | 2006
But im aiming to get it like
Name| 2004 | 2005 | 2006 .....
Bob | 0 | 6 | 88
The next issue, is getting the top 5 in total after the pivot has been done!
Is this what you want?
select top (5) name,
sum(case when year <= 2005 then ApplesEaten else 0 end) as apples_2005,
sum(case when year <= 2006 then ApplesEaten else 0 end) as apples_2006,
. . .
sum(case when year <= 2019 then ApplesEaten else 0 end) as apples_2019
from t
group by name
order by sum(ApplesEaten) desc
you can use case when
select top 5 name, max(case when year=2004 then ApplesEaten end ) [2004],
max(case when year=2005 then ApplesEaten end ) [2005],
max(case when year=2006 then ApplesEaten end ) [2006],
.......................
from table_name group by name
order by sum(ApplesEaten ) desc
Sample data
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
;WITH CTE(Name , ApplesEaten , [Year])
AS
(
SELECT 'Bob' , 2 , 2012 UNION ALL
SELECT 'Bob' , 5 , 2016 UNION ALL
SELECT 'Elvis', 1 , 2017 UNION ALL
SELECT 'Elvis', 2 , 2012 UNION ALL
SELECT 'Sam' , 8 , 2008 UNION ALL
SELECT 'Elvis', 6 , 2004 UNION ALL
SELECT 'Sam' , 24 , 2019 UNION ALL
SELECT 'Sarah', 14 , 2015 UNION ALL
SELECT 'Bob' , 6 , 2005 UNION ALL
SELECT 'Rachel', 12 , 2010 UNION ALL
SELECT 'Rachel', 10 , 2008 UNION ALL
SELECT 'Bob' , 82 , 2006
)
SELECT Name ,
ApplesEaten ,
[Year]
INTO #Temp
FROM CTE
Sql script using Dynamic Sql
DECLARE #Columns nvarchar(max),
#IsnullColumns nvarchar(max),
#Sql nvarchar(max)
SELECT #Columns = STUFF((SELECT DISTINCT ', '+QUOTENAME([Year]) FROM #Temp FOR XML PATH ('')),1,1,'')
SELECT #IsnullColumns = STUFF((SELECT DISTINCT ', '+'ISNULL(MAX('+QUOTENAME([Year])+'),''0'') AS apples_' +CAST(([Year]) AS VARCHAR(20))
FROM #Temp FOR XML PATH ('')),1,1,'')
SET #Sql ='SELECT TOp 5 Name,'+#IsnullColumns+'
FROM
(
SELECT *,SUM(ApplesEaten) OVER(PARTITION BY Name ORDER BY [Year]) AS SumApplesEaten FROM #Temp
) AS PVT
PIVOT
(
MAX(SumApplesEaten) FOR [Year] IN ('+#Columns+')
) AS PVT
GROUP BY Name
ORDER BY Name'
PRINT #Sql
EXEC (#Sql)

Count grouped values

Again I need some help.
I have a table (for the sake of simplicity) with 3 fields.
code id letter
1 2016 Pablo A
2 2017 Pablo B
3 2016 Ana B
4 2017 Pablo A
5 2018 Ana A
6 2018 Ana A
I need a query that results in
code id letterA letterB
1 2016 Pablo 1 Null
2 2017 Pablo 1 1
3 2016 Ana Null 1
4 2018 Ana 2 Null
As you can see I count the records for id and grouped by code, if they have different letters for code a new record appears, but if they have both letters on the same code is just one record.
I tried with UNION but what I got is two records (with the same code) with different letters.
Thanks guys,
Edit one:
The query with union
select code, id, count(id), 'letter A' letter
from table
where letter = 'A'
union
select code, id, count(id), 'letter B' letter
from table
where letter = 'B'
I got something like this
code id count(id) letter
1 2016 Pablo 1 A
2 2017 Pablo 1 A
3 2017 Pablo 1 B
4 2016 Ana 1 B
5 2018 Ana 2 A
The problem is that I have 2 code 2017 with id Pablo, I would like to have just 1
You almost got it. You only need another GROUP BY to get the result that you wanted.
Using PIVOT
select *
from tbl t
pivot
(
count(letter)
for letter in ([A], [B])
) p
order by id desc, code
Using Union All
select code, id, A = sum(A), B = sum(B)
from
(
select code, id, A = count(*), B = null
from tbl t
where letter = 'A'
group by code, id
union all
select code, id, A = null, B = count(*)
from tbl t
where letter = 'B'
group by code, id
) d
group by code, id
order by id desc, code
You can do this by executing a dynamic sql query rather than giving values explicitly.
Query
declare #sql as varchar(max);
select #sql = 'select [code], [id], ' + stuff((
select distinct ', sum(case [letter] when ' + char(39) + [letter] + char(39)
+ ' then 1 else 0 end) as [letter' + [letter] + '] '
from [dbo].[your_table_name]
for xml path('')
)
, 1, 2, ''
);
select #sql += ' from [dbo].[your_table_name] group by [code], [id] order by [id];';
exec(#sql);
Other approach is using CASE expression in SELECT by grouping rows.
select code,
id,
SUM(CASE WHEN letter= 'A' THEN 1 ELSE 0 END) AS 'letter A' ,
SUM(CASE WHEN letter= 'B' THEN 1 ELSE 0 END) AS 'letter B'
from table
group by code, id
Note: If there are no letters, then it returns 0 instead of NULL.

How to use SQL Server 2005 Pivot based on lookup table

table [Status] has the following data:
ID Status
1 PaymentPending
2 Pending
3 Paid
4 Cancelled
5 Error
====================================
Data Table has the following structure:
ID WeekNumber StatusID
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 2 2
7 2 3
Looking for a Pivot
Week # PaymentPending Pending Paid Cancelled
Week 1 1 1 1 0
Week 2 1 2 1 0
SELECT 'Week '+CAST(coun.WeekNumber AS VARCHAR(10)) [Week #],[PaymentPending],[Pending],[Paid],[Cancelled],[Error] FROM
(SELECT [WeekNumber],[Status] FROM dbo.WeekDetails
INNER JOIN [dbo].[Status] AS s
ON [dbo].[WeekDetails].[StatusID] = [s].[ID]) AS wee
PIVOT (COUNT(wee.[Status]) FOR wee.[Status]
IN ([PaymentPending],[Pending],[Paid],[Cancelled],[Error])) AS Coun
A pivot might look like this:
SELECT * FROM
(SELECT
'Week ' + CAST(D.WeekNumber AS varchar(2)) [Week #],
S.Status
FROM DataTbl D
INNER JOIN Status S ON D.StatusID = S.ID
) Derived
PIVOT
(
COUNT(Status) FOR Status IN
([PaymentPending], [Pending], [Paid], [Cancelled]) -- add [Error] if needed
) Pvt
If you expect the number of items in theStatustable to change you might want to consider using a dynamic pivot to generate the column headings. Something like this:
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols = ISNULL(#cols + ',','') + QUOTENAME(Status)
FROM (SELECT ID, Status FROM Status) AS Statuses ORDER BY ID
SET #sql =
N'SELECT * FROM
(SELECT ''Week '' + CAST(D.WeekNumber AS varchar(2)) [Week #], S.Status
FROM Datatbl D
INNER JOIN Status S ON D.StatusID = S.ID) Q
PIVOT (
COUNT(Status)
FOR Status IN (' + #cols + ')
) AS Pvt'
EXEC sp_executesql #sql;
Sample SQL Fiddle
You can use CASE based aggregation with GROUP BY
SELECT 'Week ' + cast(WeekNumber as varchar(10)) as 'Week#',
SUM ( CASE WHEN StatusId =1 THEN 1 else 0 end) as 'PaymentPending',
SUM ( CASE WHEN StatusId =2 THEN 1 else 0 end) as 'Pending',
SUM ( CASE WHEN StatusId =3 THEN 1 else 0 end) as 'Paid',
SUM ( CASE WHEN StatusId =4 THEN 1 else 0 end) as 'Cancelled'
FROM DataTbl D
GROUP BY 'Week ' + cast(WeekNumber as varchar(10))

SQL Multiple count on same row with dynamic column

I need to alter view that show user count(ScheduleID) by period on same row. Now the Period table content can grow and contain more than 3 periods.
The actual SQL is:
SELECT r.Code,
SUM(CASE WHEN s.PeriodID=1 THEN 1 ELSE 0 END) AS PeriodID1,
SUM(CASE WHEN s.PeriodID=2 THEN 1 ELSE 0 END) AS PeriodID2,
SUM(CASE WHEN s.PeriodID=3 THEN 1 ELSE 0 END) AS PeriodID3,
SUM(CASE WHEN s.PeriodID IN (1,2,3) THEN 1 ELSE 0 END) AS Total
FROM Schedules s
JOIN Periods p ON p.PeriodID = s.PeriodID
JOIN Resources r ON r.ResourceID = s.ResourceID
GROUP BY r.Code;
Example data:
Table Schedules
ScheduleID(int) ResourceID(int) ResourceCode(varchar 4) PeriodID(int)
1 1 AA 1
2 1 AA 3
3 1 AA 3
4 2 BB 1
5 3 CC 1
6 1 AA 1
7 3 CC 2
8 3 CC 3
9 2 BB 1
10 2 BB 2
11 2 BB 3
12 1 AA 3
Table Periods
PeriodID(int) Code (varchar 4)
1 P1
2 P2
3 P3
4 P4
5 P5
6 P6
7 P7
8 P8
The result I need is:
ResourceCode PeriodID1 PeriodID2 PeriodID3 ... PeriodID8 TOTAL
AA 2 0 3 0 5
BB 2 1 1 0 4
CC 1 1 1 0 3
The Periods table content is now dynamic.
The database version is an Microsoft SQL 2008
I like to know if is possible to do that without create stored procedure...and doing this in one query like this:
SELECT *
FROM (
SELECT R.Code, P.PeriodID, COUNT(S.ScheduleID) AS RPCount
FROM Schedules S INNER JOIN Periods P ON S.PeriodID = P.PeriodID
JOIN Resources R ON S.ResourceID = R.ResourceID
WHERE S.ResourceID is not null
GROUP BY R.Code, P.PeriodID
) as data
PIVOT
(
SUM(RPCount)
--FOR PeriodID IN ([1],[2],[3])
FOR PeriodID IN (SELECT PeriodID From Periods)
)AS pvt
ORDER BY Code
Since you are using SQL Server then you can implement the PIVOT function and if you have an unknown number of period values, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('PeriodId'+cast(periodid as varchar(10)))
from Periods
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT resourcecode, ' + #cols + ' , Total
from
(
select s.resourcecode,
''PeriodId''+cast(p.periodid as varchar(10)) period,
count(*) over(partition by s.resourcecode) Total
from periods p
left join schedules s
on p.periodid = s.periodid
) x
pivot
(
count(period)
for period in (' + #cols + ')
) p
where resourcecode is not null
order by resourcecode'
execute(#query)
See SQL Fiddle with Demo. This gives a result:
| RESOURCECODE | PERIODID1 | PERIODID2 | PERIODID3 | PERIODID4 | PERIODID5 | PERIODID6 | PERIODID7 | PERIODID8 | TOTAL |
------------------------------------------------------------------------------------------------------------------------
| AA | 2 | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 5 |
| BB | 2 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 4 |
| CC | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 3 |
Based on your previous question that was tagged with MySQL, I am assuming you are using MySQL as the database. If so, then you do not have a PIVOT function so you will have to use an aggregate function with a CASE expression to transform the rows of data into columns.
If your column values are known, then you can hard-code the query:
select resourcecode,
sum(case when period = 'PeriodId1' then 1 else 0 end) PeriodId1,
sum(case when period = 'PeriodId2' then 1 else 0 end) PeriodId2,
sum(case when period = 'PeriodId3' then 1 else 0 end) PeriodId3,
sum(case when period = 'PeriodId4' then 1 else 0 end) PeriodId4,
sum(case when period = 'PeriodId5' then 1 else 0 end) PeriodId5,
sum(case when period = 'PeriodId6' then 1 else 0 end) PeriodId6,
sum(case when period = 'PeriodId7' then 1 else 0 end) PeriodId7,
sum(case when period = 'PeriodId8' then 1 else 0 end) PeriodId8,
count(*) Total
from
(
select concat('PeriodId', p.periodid) Period,
s.resourcecode
from periods p
left join schedules s
on p.periodid = s.periodid
) d
where resourcecode is not null
group by resourcecode;
See SQL Fiddle with Demo. But if the values will be unknown or dynamic then you will need to use a prepared statement to generate a sql string to execute:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN period = ''',
concat('PeriodId', periodid),
''' THEN 1 else 0 END) AS `',
concat('PeriodId', periodid), '`'
)
) INTO #sql
FROM periods;
SET #sql
= CONCAT('SELECT resourcecode, ', #sql, ' , count(*) Total
from
(
select concat(''PeriodId'', p.periodid) Period,
s.resourcecode
from periods p
left join schedules s
on p.periodid = s.periodid
) d
where resourcecode is not null
group by resourcecode');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo.
Use PIVOT
try this
SELECT *
FROM (
SELECT
S.ResourceCode ,
P.PeriodID AS period,
COUNT(*) AS PCount
FROM Schedules S INNER JOIN Periods P ON S.PeriodID =P.PeriodID
GROUP BY S.ResourceCode ,P.PeriodID
) as s
PIVOT
(
PCount,
FOR [period] IN (SELECT DISTINCT PeriodID From Periods)
)AS pivot
Please try below code for MS Sql server:
DECLARE #column VARCHAR(MAX), #SumQuery VARCHAR(MAX)
SELECT
#column = COALESCE(#column + '], [', '')+ CAST(PeriodID as nvarchar(10)),
#SumQuery = COALESCE(#SumQuery + ']+[', '')+ CAST(PeriodID as nvarchar(10))
FROM
Periods
GROUP BY PeriodID
EXEC ('select *, ['+#SumQuery+'] as [Total] From
(
select * From Schedules
)up
pivot (count(ScheduleID) for PeriodID in (['+#column+'])) as pvt')

Counting values in a column separately

I have a table in my database with the following structure.
ID COMPANY_ID Status
-----------------------
1 10 1
2 10 2
3 12 2
4 12 2
5 12 1
6 13 3
7 14 3
8 14 3
9 10 1
10 10 2
I want to group my results on company ID and count each status and list them as separate columns.
i.e.
COMPANY_ID Status 1 Status 2 Status 3
-------------------------------------------
10 2 2 0
12 1 2 0
13 0 0 1
14 0 0 2
My question is how do I get the results above from my table? and probably join in with the company table.
Tried several possibilities, but didn't get the results.
select company_id
, count(case when status = 1 then 1 end) as [Status 1]
, count(case when status = 2 then 1 end) as [Status 2]
, count(case when status = 3 then 1 end) as [Status 3]
from YourTable
group by
company_id
This type of data transformation is known as a PIVOT. There are several ways that you are pivot the data.
You can use an aggregate function with a CASE expression:
select company_id,
sum(case when status = 1 then 1 else 0 end) status1,
sum(case when status = 2 then 1 else 0 end) status2,
sum(case when status = 3 then 1 else 0 end) status3
from yourtable
group by company_id;
See SQL Fiddle with Demo
Starting in SQL Server 2005+ you can use the PIVOT function:
select company_id,
[1] as Status1,
[2] as Status2,
[3] as Status3
from
(
select company_id, status
from yourtable
)src
pivot
(
count(status)
for status in ([1], [2], [3])
) piv
See SQL Fiddle with Demo.
The two versions above work well if you have a known number of values to transform into columns. But if it is unknown, then you can use dynamic SQL to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Status'+cast(status as varchar(10)))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT company_id,' + #cols + ' from
(
select company_id, ''Status''+cast(status as varchar(10)) Status
from yourtable
) x
pivot
(
count(Status)
for Status in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
All give the result:
| COMPANY_ID | STATUS1 | STATUS2 | STATUS3 |
--------------------------------------------
| 10 | 2 | 2 | 0 |
| 12 | 1 | 2 | 0 |
| 13 | 0 | 0 | 1 |
| 14 | 0 | 0 | 2 |