Combining several similar queries - mysql

How can I combine these three queries into one?
SELECT COUNT(*)
FROM Users
WHERE (SELECT COUNT(*) FROM Posts WHERE Posts.OwnerUserId = Users.Id) < 10;
SELECT COUNT(*)
FROM Users
WHERE (SELECT COUNT(*) FROM Posts WHERE Posts.OwnerUserId = Users.Id) BETWEEN 10 AND 20;
SELECT COUNT(*)
FROM Users
WHERE (SELECT COUNT(*) FROM Posts WHERE Posts.OwnerUserId = Users.Id) > 20;

If I follow you correctly, you can use two levels of aggregation. The following query puts each bucket in a separate row:
select
case
when cnt < 10 then '< 10'
when cnt < 20 then '10-20'
else '> 20'
end as bucket,
count(*) cnt
from (
select count(p.owneruserid) cnt
from users u
left join posts p on p.owneruserid = u.id
group by u.id
) t
group by case
when cnt < 10 then '< 10'
when cnt < 20 then '10-20'
else '> 20'
end
Or you can get all three counts on the same row as follows:
select
sum(case when cnt < 10 then 1 else 0 end) as cnt_less_than_10,
sum(case when cnt >= 10 and cnt < 20 then 1 else 0 end) as cnt_10_to_20,
sum(case when cnt > 20 then 1 else 0 end) as cnt_more_than_20
from (
select count(*) cnt
from users u
inner join posts p on p.owneruserid = u.id
group by u.id
) t

Related

Count number of Users with more then 3 posts

I have 2 tables Users and Posts and I need only number of users who have more than 3 posts and who have more than 5 posts.
I need something like this:
NumberOfUsers
Posts
555
>3
888
>5
SELECT
COUNT( u.Id)
FROM
Users u
INNER JOIN Posts p ON (u.Id=p.OwnerId)
HAVING COUNT(p.Id)>3
I try this but having count does not work I think.
You can use two levels of aggregation
select sum(case when cnt > 3 then 1 else 0 end) cnt_3,
sum(case when cnt > 5 then 1 else 0 end) cnt_5
from (select ownerid, count(*) cnt from posts group by ownerid) p
Note that you don't need to bring in the users table ; the posts table has all the information we need.
Depending on your database, syntax shortcuts might be available, as in MySQL :
select sum(cnt > 3) cnt_3,
sum(cnt > 5) cnt_5
from (select ownerid, count(*) cnt from posts group by ownerid) p
Or in Posgres :
select count(*) where(cnt > 3) cnt_3,
count(*) where(cnt > 5) cnt_5
from (select ownerid, count(*) cnt from posts group by ownerid) p

how to fetch top 9 and others based on count(entity_id) sorted highest top 9 and then club the other count into 'others' category

The following query executes everything:
SELECT count(entity_id), cim.institute_id, cfi.name_ FROM ci_certification_students cis INNER JOIN ci_certification_master cim ON cis.certificate_id = cim.certificate_id
INNER JOIN ci_finalized_institute cfi ON cfi.institute_id=cim.institute_id
WHERE cis.status_id=4 AND cim.institute_id is not null group by cim.institute_id order by count(entity_id) desc limit 100;
I tried the following:
SELECT seqnum,institute_id, (CASE WHEN seqnum > 9 THEN 'others' ELSE name_ END) as name_, sum(total_certi) FROM
(select count(cis.entity_id) as total_certi, cim.institute_id, cfi.name_ as certiName, ROW_NUMBER() OVER (ORDER BY COUNT(cis.entity_id) desc) as seqnum
FROM certificationdb.ci_certification_students cis
INNER JOIN ci_certification_master cim ON cis.certificate_id = cim.certificate_id
INNER JOIN certificationdb.ci_finalized_institute cfi ON cim.institute_id=cfi.institute_id
WHERE cis.status_id=4 AND cim.institute_id is not null group by cim.institute_id ORDER BY
count(cis.entity_id)desc)c GROUP BY (CASE WHEN seqnum> 9 THEN 'others' ELSE name_ END)
ORDER BY seqnum;
Any help would be appreciated...
Use row_number() and two levels of aggregation:
SELECT (CASE WHEN seqnum < 10 THEN institute_id END) as institute_id,
(CASE WHEN seqnum < 10 THEN name_ ELSE 'OTHERS' END) as name,
SUM(cnt) as cnt
FROM (SELECT cim.institute_id, cfi.name_, COUNT(*) as cnt,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM ci_certification_students cis INNER JOIN
ci_certification_master cim
ON cis.certificate_id = cim.certificate_id INNER JOIN
ci_finalized_institute cfi ON
cfi.institute_id = cim.institute_id
WHERE cis.status_id = 4 AND cim.institute_id is not null
GROUP BY cim.institute_id, cfi.name_
) c
GROUP BY institute_id, name
ORDER BY MIN(seqnum);

MySql GROUP BY Max Date

I have a table called votes with 4 columns: id, name, choice, date.
****id****name****vote******date***
****1*****sam*******A******01-01-17
****2*****sam*******B******01-05-30
****3*****jon*******A******01-01-19
My ultimate goal is to count up all the votes, but I only want to count 1 vote per person, and specifically each person's most recent vote.
In the example above, the result should be 1 vote for A, and 1 vote for B.
Here is what I currently have:
select name,
sum(case when uniques.choice = A then 1 else 0 end) votesA,
sum(case when uniques.choice = B then 1 else 0 end) votesB
FROM (
SELECT id, name, choice, max(date)
FROM votes
GROUP BY name
) uniques;
However, this doesn't work because the subquery is indeed selecting the max date, but it's not including the correct choice that is associated with that max date.
Don't think "group by" to get the most recent vote. Think of join or some other option. Here is one way:
SELECT v.name,
SUM(v.choice = 'A') as votesA,
SUM(v.choice = 'B') as votesB
FROM votes v
WHERE v.date = (SELECT MAX(v2.date) FROM votes v2 WHERE v2.name = v.name)
GROUP BY v.name;
Here is a SQL Fiddle.
Your answer are close but need to JOIN self
Subquery get Max date by name then JOIN self.
select
sum(case when T.vote = 'A' then 1 else 0 end) votesA,
sum(case when T.vote = 'B' then 1 else 0 end) votesB
FROM (
SELECT name,Max(date) as date
FROM T
GROUP BY name
) AS T1 INNER JOIN T ON T1.date = T.date
SQLFiddle
Try this
SELECT
choice,
COUNT(1)
FROM
votes v
INNER JOIN
(
SELECT
id,
max(date)
FROM
votes
GROUP BY
name
) tmp ON
v.id = tmp.id
GROUP BY
choice;
Something like this (if you really need count only last vote of person)
SELECT
sum(case when vote='A' then cnt else 0 end) voteA,
sum(case when vote='B' then cnt else 0 end) voteB
FROM
(SELECT vote,count(distinct name) cnt
FROM (
SELECT name,vote,date,max(date) over (partition by name) maxd
FROM votes
)
WHERE date=maxd
GROUP BY vote
)
PS. MySQL v 8
select
name,
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
group by name
Or output just one row for the total counts of VoteA and VoteB:
select
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
Based on #d-shish solution, and since introduction (in MySQL 5.7) of ONLY_FULL_GROUP_BY, the GROUP BY statement must be placed in subquery like this :
SELECT v.`name`,
SUM(v.`choice` = 'A') as `votesA`,
SUM(v.`choice` = 'B') as `votesB`
FROM `votes` v
WHERE (
SELECT MAX(v2.`date`)
FROM `votes` v2
WHERE v2.`name` = v.`name`
GROUP BY v.`name` # << after
) = v.`date`
# GROUP BY v.`name` << before
Otherwise, it won't work anymore !

SQL JOIN COUNT then using that count value in where clause

I'm trying to select if a user rating (user.rating) is greater then 6 or if the user has more then 100 transactions (transaction table count). Basically count how many transactions the user has then where (transaction count >= 100 OR user rating >= 6).
SELECT *
FROM `user`
JOIN (SELECT COUNT(*)
FROM transaction
WHERE transaction.user_id=user.id
AND type='L'
AND status='S') AS tcount
WHERE (user.rating >= '6' OR tcount >= '100')
Just another possible answer. I've created simplified schemas to test it, please try it and let me know the result.
SELECT *
FROM user
WHERE user.rating >= 6 OR (SELECT COUNT(*) FROM transaction WHERE user_id = user.id and type = 'L' and status = 'S') >= 100;
Use an alias on COUNT(*)
SELECT *
FROM `user`
JOIN (SELECT user_id, COUNT(*) cnt
FROM transaction
WHERE type='L'
AND status='S'
GROUP BY user_id) AS tcount
ON user.id = tcount.user_id
WHERE (user.rating >= '6' OR tcount.cnt >= '100')
You can write that without the subquery, like this
SELECT u.id
FROM `user` u
JOIN `transaction` t
ON t.user_id=u.id
WHERE t.type = 'L' AND t.status = 'S'
GROUP BY u.id
HAVING sum(case when u.rating >= 6 then 1 end) > 0 OR count(*) >= 100

SQL case subquery + subquery > 0

i am trying to devide a number with another number. but because there is a chance that the result is 0 i want to make a case saying that if it is 0 then it should be 1 for this ive created this:
(CASE(SELECT COUNT(*)
FROM module_score
WHERE user_id = 40 AND medal_id > 1)
+
(SELECT COUNT(*)
FROM user_has_module_score uhms
WHERE user_id = 40 and medal_id > 1)> 0
THEN 1
ELSE 0 END) as passed_percentage
However i get the following syntax error:
medal_id > 1)> 0 THEN 1 ELSE 0 END) as passed_percentage
FROM system_learningbank.user U
WHERE U.id = 40 GROUP BY U.id
Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'THEN 1 ELSE 0 END) as passed_percentage FROM system_learningbank.user U WH' at line 7
So what is the right syntax if to validate that it is not 0? and in my above example i set the variable to 1 but that should be the actual count of the subqueries
My full sql statement
SELECT
(SELECT
COUNT(*)
FROM
module_score MS
WHERE
user_id = 40) + (SELECT
COUNT(UHMS.score)
FROM
user_has_module_score UHMS
WHERE
UHMS.user_id = 40) / ((SELECT
COUNT(*)
FROM
module_score
WHERE
user_id = 40 AND medal_id > 1) + (SELECT
COUNT(*)
FROM
user_has_module_score uhms
WHERE
user_id = 40 and medal_id > 1) > 0) as passed_percentageas
FROM
system_learningbank.user U
WHERE
U.id = 40
GROUP BY U.id;
You are missing the when:
(CASE WHEN (SELECT COUNT(*) FROM module_score WHERE user_id = 40 AND medal_id > 1) +
(SELECT COUNT(*) FROM user_has_module_score uhms WHERE user_id = 40 and medal_id > 1) > 0
THEN 1 ELSE 0
END) as passed_percentage
MySQL treats booleans as integers, so you can actually write this without the case:
( (SELECT COUNT(*) FROM module_score WHERE user_id = 40 AND medal_id > 1) +
(SELECT COUNT(*) FROM user_has_module_score uhms WHERE user_id = 40 and medal_id > 1) > 0
) as passed_percentage
EDIT:
SELECT (SELECT COUNT(*)
FROM module_score MS
WHERE user_id = 40
) +
(SELECT COUNT(UHMS.score)
FROM user_has_module_score UHMS
WHERE UHMS.user_id = 40
) / ((SELECT COUNT(*)
FROM module_score
WHERE user_id = 40 AND medal_id > 1
) +
(SELECT COUNT(*)
FROM user_has_module_score uhms
WHERE user_id = 40 and medal_id > 1
) > 0
) as passed_percentageas
FROM system_learningbank.user U
WHERE U.id = 40
GROUP BY U.id;
I suspect you want something more like this:
SELECT ((ms.cnt + hms.cnt) /
(case when ms.cnt_m1 + hms.cnt_m2 > 0 then ms.cnt_m1 + hms.cnt_m2 end)
) as passed_percentageas
FROM system_learningbank.user U left join
(select userid, count(*) as cnt, sum(medal_id > 1) as cnt_m1
from module_score
group by user_id
) ms
on ms.user_id = u.user_id left join
(select userid, count(*) as cnt, sum(medal_id > 1) as cnt_m1
from user_has_module_score
group by user_id
) hms
on hms.user_id = u.user_id
WHERE U.id = 40;
However, this still looks suspicious . . . the table names suggest that only one really codes the score you want, the denominator is more restrictive than the numerator, but this structure is basically what you seem to want.
GROUP BY U.id;