Combining two aggregate queries into one - sql

For some context, I am making an image browser which is connected to an SQLite database. Within the browser, similar images are grouped into an event (EventId) and each image (MicrosoftId) is labelled with a few tags (name).
I have these two queries on the same table (TagsMSCV) but pulling out different information. Ultimately I need to combine the information in my browser so if it was possible to combine these two queries (maybe with a JOIN?) it would be a lot faster and convenient for me. Both results of these queries share the EventId column.
1st Query ():
SELECT EventId as 'event', count(*) as 'size',
SUM(case when tag_count = 1 then 1 else 0 end) as '1',
SUM(case when tag_count = 2 then 1 else 0 end) as '2',
SUM(case when tag_count = 3 then 1 else 0 end) as '3'
FROM (SELECT EventId, MicrosoftId,
SUM(case when name in ('indoor', 'cluttered', 'screen') then 1 else 0 end) as tag_count
FROM TagsMSCV GROUP BY EventId, MicrosoftId) TagsMSCV
GROUP BY EventId ORDER BY 3 DESC, 2 DESC, 1 DESC
2nd Query
SELECT EventId,
SUM(CASE WHEN name = 'indoor' THEN 1 ELSE 0 END) as indoor,
SUM(CASE WHEN name = 'cluttered' THEN 1 ELSE 0 END) as cluttered,
SUM(CASE WHEN name = 'screen' THEN 1 ELSE 0 END) as screen
FROM TagsMSCV WHERE name IN ('indoor', 'cluttered', 'screen')
GROUP BY EventId
As you can see in both queries I am feeding in the tags 'necktie' 'man', 'male' and getting different information back.
SQL Fiddle Here: https://www.db-fiddle.com/f/f8WNimjmZAj1XXeCj4PHB8/3

You should do this all in one query:
SELECT EventId as event, count(*) as size,
SUM(case when (indoor + cluttered + screen) = 1 then 1 else 0 end) as tc_1,
SUM(case when (indoor + cluttered + screen) = 2 then 1 else 0 end) as tc_2,
SUM(case when (indoor + cluttered + screen) = 3 then 1 else 0 end) as tc_3,
SUM(indoor) as indoor,
SUM(cluttered) as cluttered,
SUM(screen) as screen
FROM (SELECT EventId, MicrosoftId,
SUM(CASE WHEN name = 'indoor' THEN 1 ELSE 0 END) as indoor,
SUM(CASE WHEN name = 'cluttered' THEN 1 ELSE 0 END) as cluttered,
SUM(CASE WHEN name = 'screen' THEN 1 ELSE 0 END) as screen
FROM TagsMSCV
GROUP BY EventId, MicrosoftId
) TagsMSCV
GROUP BY EventId
ORDER BY 3 DESC, 2 DESC, 1 DESC;
You need two aggregations to get the information about the tag counts. There is no need to add more aggregations and joins to the query.

You could use an Inner join subquery
SELECT TagsMSCV.EventId as 'event', count(*) as 'size',
SUM(case when tag_count = 1 then 1 else 0 end) as '1',
SUM(case when tag_count = 2 then 1 else 0 end) as '2',
SUM(case when tag_count = 3 then 1 else 0 end) as '3',
t.necktie,
t.man,
t.male
FROM (
SELECT EventId, MicrosoftId,
SUM(case when name in ('necktie' 'man', 'male') then 1 else 0 end) as tag_count
FROM TagsMSCV GROUP BY EventId, MicrosoftId
) TagsMSCV
INNER JOIN (
SELECT EventId,
SUM(CASE WHEN name = 'necktie' THEN 1 ELSE 0 END) as necktie,
SUM(CASE WHEN name = 'man' THEN 1 ELSE 0 END) as man,
SUM(CASE WHEN name = 'male' THEN 1 ELSE 0 END) as male
FROM TagsMSCV WHERE name IN ('necktie' 'man', 'male')
GROUP BY EventId
) t on t.EventId = TagsMSCV.EventId
GROUP BY TagsMSCV.EventId
ORDER BY 3 DESC, 2 DESC, 1 DESC

Related

Trying to introduce a simple new condition to a JOIN query

I am using SQLite and I had someone help me construct this JOIN query which works quite well, but now I need to add another condition but I am having trouble introducing it to the query without it breaking.
In both tables used in the JOIN there is a column called EventId and I want to introduce the simple condition...
WHERE EventId = 123456
Below you can see a working example of the query itself along with two comments where I have tried to introduce the new condition and failed (because I'm bad at SQL).
SELECT t.MicrosoftId,
SUM(CASE WHEN name = 'necktie' THEN 1 ELSE 0 END) as 'necktie',
SUM(CASE WHEN name = 'shirt' THEN 1 ELSE 0 END) as 'shirt',
SUM(CASE WHEN name = 'suit' THEN 1 ELSE 0 END) as 'suit',
SUM(CASE WHEN name = 'man' THEN 1 ELSE 0 END) as 'man',
SUM(CASE WHEN name = 'male' THEN 1 ELSE 0 END) as 'male'
FROM TagsMSCV t
/* <---- WHERE t.EventId = 123456 (fails here...) */
LEFT JOIN
(SELECT i.MicrosoftId
FROM Images i
GROUP BY i.MicrosoftId) i
ON i.MicrosoftId = t.MicrosoftId
WHERE t.name IN ('necktie','shirt','suit','man','male')
/* <---- AND WHERE t.EventId = 123456 (fails here too...) */
GROUP BY t.MicrosoftId
try like below
select t1.* from ( SELECT t.MicrosoftId,
SUM(CASE WHEN name = 'necktie' THEN 1 ELSE 0 END) as 'necktie',
SUM(CASE WHEN name = 'shirt' THEN 1 ELSE 0 END) as 'shirt',
SUM(CASE WHEN name = 'suit' THEN 1 ELSE 0 END) as 'suit',
SUM(CASE WHEN name = 'man' THEN 1 ELSE 0 END) as 'man',
SUM(CASE WHEN name = 'male' THEN 1 ELSE 0 END) as 'male'
FROM TagsMSCV t WHERE t.EventId = 123456
and name IN ('necktie','shirt','suit','man','male') group by t.MicrosoftId
) t1
You did mistake to create subquery and as 2nd subquery no need group by as there no aggregate function used
It should be in WHERE section, but without second WHERE keyword:
SELECT t.MicrosoftId,
SUM(CASE WHEN name = 'necktie' THEN 1 ELSE 0 END) as 'necktie',
SUM(CASE WHEN name = 'shirt' THEN 1 ELSE 0 END) as 'shirt',
SUM(CASE WHEN name = 'suit' THEN 1 ELSE 0 END) as 'suit',
SUM(CASE WHEN name = 'man' THEN 1 ELSE 0 END) as 'man',
SUM(CASE WHEN name = 'male' THEN 1 ELSE 0 END) as 'male'
FROM TagsMSCV t
LEFT JOIN
(SELECT i.MicrosoftId
FROM Images i
GROUP BY i.MicrosoftId) i
ON i.MicrosoftId = t.MicrosoftId
WHERE t.name IN ('necktie','shirt','suit','man','male')
AND t.EventId = 123456
GROUP BY t.MicrosoftId

How to calculate a Cumulative total using SQL

I have a Tickets table in My database , each Ticket have a status_id (1,2,3)
1: Ticket IN PROGRESS
2: Ticket Out Of time
3: Ticket Closed
I want using SQL to calculate the number of tickets for each status .
Calculate the cumulative total for each Status in a specific Date, I have already a column affectation_Date that contains the date where the status of ticket has been changed .
Use conditional aggregation as
SELECT TicketID,
AffectationDate,
SUM(CASE WHEN StatusID = 1 THEN 1 ELSE 0 END) InProgress,
SUM(CASE WHEN StatusID = 2 THEN 1 ELSE 0 END) OuOfTime,
SUM(CASE WHEN StatusID = 3 THEN 1 ELSE 0 END) Closed,
COUNT(1) Total
FROM Tickets
GROUP BY TicketID,
AffectationDate
ORDER BY TicketID,
AffectationDate;
Or if you want to GROUP BY AffectationDate only
SELECT AffectationDate,
SUM(CASE WHEN StatusID = 1 THEN 1 ELSE 0 END) TotalInProgress,
SUM(CASE WHEN StatusID = 2 THEN 1 ELSE 0 END) TotalOutOfTime,
SUM(CASE WHEN StatusID = 3 THEN 1 ELSE 0 END) TotalClosed,
COUNT(1) TotalStatusThisDate
FROM Tickets
GROUP BY AffectationDate
ORDER BY AffectationDate;
Live Demo
Using conditional counts.
SELECT affectation_Date,
COUNT(CASE WHEN status_id = 1 THEN 1 END) AS TotalInProgress,
COUNT(CASE WHEN status_id = 2 THEN 1 END) AS TotalOutOfTime,
COUNT(CASE WHEN status_id = 3 THEN 1 END) AS TotalClosed
FROM Tickets t
GROUP BY affectation_Date
ORDER BY affectation_Date
you may use the desired filter condition for the date criteria
SELECT COUNT(1), STATUS
FROM tickets
WHERE affectation_Date >= 'someDate'
group by status
Regards
You just need to group by status and count the number of tickets in each group:
select status, count(*) as number
from Tickets
where dt >= '2019-01-01 00:00:00' and dt < '2019-01-02 00:00:00'
group by status
having status >= 1 and status <= 3
This adds the Cumulative Sum to the existing answers:
SELECT AffectationDate,
Sum(CASE WHEN StatusID = 1 THEN 1 ELSE 0 END) AS TotalInProgress,
Sum(CASE WHEN StatusID = 2 THEN 1 ELSE 0 END) AS TotalOutOfTime,
Sum(CASE WHEN StatusID = 3 THEN 1 ELSE 0 END) AS TotalClosed,
Count(*) as TotalStatusThisDate,
Sum(Sum(CASE WHEN StatusID = 1 THEN 1 ELSE 0 END)) Over (ORDER BY AffectationDate) AS cumTotalInProgress,
Sum(Sum(CASE WHEN StatusID = 2 THEN 1 ELSE 0 END)) Over (ORDER BY AffectationDate) AS cumTotalOutOfTime,
Sum(Sum(CASE WHEN StatusID = 3 THEN 1 ELSE 0 END)) Over (ORDER BY AffectationDate) AS cumTotalClosed,
Sum(Count(*)) Over (ORDER BY AffectationDate) AS cumTotalStatusThisDate
FROM Tickets
GROUP BY AffectationDate
ORDER BY AffectationDate;

Exclude records that have sum greater than 1

I have query returning details of customers that are subscribed to channel xyz or all other channels.
To generate this results i am using the following query:
select customerID
,sum(case when channel='xyz' then 1 else 0 end) as 'xyz Count'
,sum(case when channel<>'xyz' then bundle_qty else 0 end) as 'Other'
From temptable
So my Question is, how do i Exclude customers that are subscribed to 2 channels, where one is xyz and one is another channel.
select customerID
,sum(case when channel='xyz' then 1 else 0 end) as 'xyz Count'
,sum(case when channel<>'xyz' then bundle_qty else 0 end) as 'Other'
From temptable
group by customerID
having sum(case when channel= 'xyz' then 1 else 0 end) > 0
and sum(case when channel<>'xyz' then 1 else 0 end) > 0
First, your query is not correct. It needs a group by. Second, you can do what you want using having:
select customerID,
sum(case when channel = 'xyz' then 1 else 0 end) as xyz_Count,
sum(case when channel<>'xyz' then bundle_qty else 0 end) as Other
From temptable
group by customerID
having count(*) = 2 and
sum(case when channel = 'xyz' then 1 else 0 end) = 1;
If customers can subscribe to the same channel multiple times, and you still want only "xyz" and another channel, then:
having count(distinct channel) = 2 and
(min(channel) = 'xyz' or max(channel) = 'xyz')

How to get summary totals on unique IDs using analytics?

My repeating table has duplicate ids but I want summary statistics on unique ids.
Detail_id code book tree
----------- ------ ------ ------
1 BR54 COOK PINE
1 BR55 COOK PINE
1 BR51 COOK PINE
2 BR55 COOK MAPL
2 BR60 COOK MAPL
3 BR61 FORD PINE
3 BR54 FORD PINE
3 BR55 FORD PINE
Here's my query which is also on SQLFiddle
select count(case detail_book when 'COOK' THEN 1 else 0 end) as cook_total,
count(case detail_book when 'FORD' THEN 1 else 0 end) as ford_total,
count(case detail_tree when 'PINE' THEN 1 else 0 end) as pine_total,
count(case detail_book when 'MAPL' THEN 1 else 0 end) as mapl_total
from detail_records;
Desired results:
COOK_TOTAL FORD_TOTAL PINE_TOTAL MAPL_TOTL
---------- ---------- ---------- ----------
2 1 2 1
You could use analytic functions and an inline view to avoid the duplicate counting issue:
select sum(case when detail_book = 'COOK' and book_cntr = 1 then 1 else 0 end) as cook_total,
sum(case when detail_book = 'FORD' and book_cntr = 1 then 1 else 0 end) as ford_total,
sum(case when detail_tree = 'PINE' and tree_cntr = 1 then 1 else 0 end) as pine_total,
sum(case when detail_tree = 'MAPL' and tree_cntr = 1 then 1 else 0 end) as mapl_total
from (select d.*,
row_number() over(partition by detail_book, detail_id order by detail_book, detail_id) as book_cntr,
row_number() over(partition by detail_tree, detail_id order by detail_tree, detail_id) as tree_cntr
from detail_records d) v
Fiddle: http://sqlfiddle.com/#!4/889a8/31/0
I don't think you need analytic functions here:
SELECT COUNT(DISTINCT CASE WHEN detail_book = 'COOK' THEN detail_id END) AS cook_total
, COUNT(DISTINCT CASE WHEN detail_book = 'FORD' THEN detail_id END) AS ford_total
, COUNT(DISTINCT CASE WHEN detail_tree = 'PINE' THEN detail_id END) AS pine_total
, COUNT(DISTINCT CASE WHEN detail_tree = 'MAPL' THEN detail_id END) AS mapl_total
FROM detail_records;
The CASE statement returns NULL when the values don't match; those aren't counted.
Updated SQL Fiddle here. By the way, in your query you were trying to match detail_book to MAPL when I think you wanted to match detail_tree, and my query reflects that.
This answer is based on your example http://sqlfiddle.com/#!4/889a8/29 which you can use to avoid the duplicate ids by getting the distinct ids for detail_book and detail_tree
Kindly check the result here http://sqlfiddle.com/#!4/889a8/44
select sum(case detail_book
when 'COOK' THEN 1
else 0
end) as cook_total,
sum(case detail_book
when 'FORD' THEN 1
else 0
end) as ford_total,
sum(case detail_tree
when 'PINE' THEN 1
else 0
end) as pine_total,
sum(case detail_tree
when 'MAPL' THEN 1
else 0
end) as mapl_total
from
(select distinct detail_id,detail_book,detail_tree
from
detail_records);
You can modify your query to get what you want just by removing the else clause:
select count(case detail_book when 'COOK' THEN 1 end) as cook_total,
count(case detail_book when 'FORD' THEN 1 end) as ford_total,
count(case detail_tree when 'PINE' THEN 1 end) as pine_total,
count(case detail_book when 'MAPL' THEN 1 end) as mapl_total
from detail_records;
The default for case without an else is NULL, so the count() works. Personally, I prefer sum() for this type of aggregation:
select sum(case when detail_book = 'COOK' THEN 1 else 0 end) as cook_total,
sum(case when detail_book = 'FORD' THEN 1 else 0 end) as ford_total,
sum(case when detail_tree = 'PINE' THEN 1 else 0 end) as pine_total,
sum(case when detail_book = 'MAPL' THEN 1 else 0 end) as mapl_total
from detail_records;
Apart from analytical functions, I'd probably use an approach by first "flattening the table" (union all) and then pivoting the result:
select *
from (
select detail_book i
from detail_records
group by detail_id, detail_book
union all
select detail_tree
from detail_records
group by detail_id, detail_tree
)
pivot(count(i) for i in ('COOK', 'FORD', 'PINE', 'MAPL'))
;
sql fiddle
select *
from (
select decode(detail_book,'FORD','FORD_TOTAL','COOK','COOK_TOTAL','MAPL','MAPL_TOTAL','PINE','PINE_TOTAL','OTHER') i,
count(distinct detail_id) j
from detail_records
group by detail_book
union all
select DECODE(detail_tree,'FORD','FORD_TOTAL','COOK','COOK_TOTAL','MAPL','MAPL_TOTAL','PINE','PINE_TOTAL','OTHER') i,
count(distinct detail_id) j
from detail_records
group by detail_tree
)
pivot(sum(j) for i in ('COOK_TOTAL', 'FORD_TOTAL', 'PINE_TOTAL', 'MAPL_TOTAL','OTHER'))
;

GROUP BY WEEK with SQL

I have the following:
http://sqlfiddle.com/#!6/226ae/1
I'm trying to now add one row for each week of the year, and filter the contacts accordingly. CONTACTS has a datetime column. The new table will look like:
Status 1 Status 2 Status 3
Week 1 3 4 2
Week 2 1 5 3
Week 3 2 2 4
I think that DATEADD needs to be used, however I'm at a loss in terms of how to begin changing my query.
I do know that MySQL has a GROUP BY WEEK command, but I don't think that SQL has an equivalent. What's the best way to accomplish this?
You can use DATEPART(), this groups by both the week and the year in the event you have data spanning multiple years:
SELECT
'Week ' + cast(datepart(wk, created) as varchar(2)) Week,
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,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
group by datepart(wk, created), year(created)
See SQL Fiddle with Demo
Adding the year to the final result:
SELECT
'Week ' + cast(datepart(wk, created) as varchar(2)) Week,
year(created) year,
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,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
group by datepart(wk, created), year(created)
See SQL Fiddle with demo
You can use the datepart function to extract the week from a date.
The query becomes:
SELECT datepart(week, created) as week,
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,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
group by datepart(week, created)
SqlFiddle: http://sqlfiddle.com/#!6/226ae/6tsq
You might try using the group by clause in your query:
SELECT
DATE_FORMAT( created, '%u' ) week_number,
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,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
GROUP BY DATE_FORMAT( created, '%u' )
I'm assuming you are talking about mysql.
The DATE_FORMAT function is documented here:
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-format