My table contains votes of users for different items. It has the following columns:
id, user_id, item_id, vote, utc_time
Only id is a unique field and the combination of user_id and utc_time is probably also unique. But user can cast votes for any item many times.
A vote is not a number but rather has one of several possible values (e.g., "awful", "bad", "good", "excellent").
I need to count how many different users cast their last vote for a given #item# as "excellent", as "good", etc. So assuming I have only four different possible vote values, I need to get four records with the following fields:
vote, count_of_users
I understand how to count all votes, not only last votes of users:
SELECT vote, COUNT(id) FROM votes WHERE item_id=#item# GROUP BY vote;
But I cannot figure out how to count only the votes where utc_time = MAX(utc_time) for each user... Thanks for your help.
This question is connected to the previous question of mine: Select one row with MAX(column) for known other several columns without subquery
try this solution if it fits with you,
SELECT a.item_ID,
SUM(CASE WHEN a.vote = 'awful' THEN 1 ELSE 0 END) awful,
SUM(CASE WHEN a.vote = 'bad' THEN 1 ELSE 0 END) bad,
SUM(CASE WHEN a.vote = 'good' THEN 1 ELSE 0 END) good,
SUM(CASE WHEN a.vote = 'excellent' THEN 1 ELSE 0 END) excellent
FROM tableName a
INNER JOIN
(
SELECT user_ID, MAX(utc_time) max_time
FROM tableName
GROUP BY user_ID
) b ON a.user_ID = b.user_ID AND
a.utc_time = b.max_time
-- WHERE a.item_ID = 'valueHere'
GROUP BY a.item_ID
UPDATE 1
SELECT a.item_ID,
a.vote,
COUNT(*) totalCount
FROM tableName a
INNER JOIN
(
SELECT user_ID, MAX(utc_time) max_time
FROM tableName
WHERE item_id = 'valueHere'
GROUP BY user_ID
) b ON a.user_ID = b.user_ID AND
a.utc_time = b.max_time
GROUP BY a.vote
Related
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 !
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
I have this sql query which is combining of two query,for a specific user in a unique matter (named title) first return the count and sum of scores where they are negative and second return sum and count of positive scores:
SELECT *
FROM (
SELECT title,
count(*) cnt_neg,
sum(score) sum_neg
FROM scores_ofexpert
WHERE user_id = "30"
AND title = "137"
AND score < 0
GROUP BY title
) neg,
(
SELECT count(*) cnt_pos,
sum(score) sum_pos
FROM scores_ofexpert
WHERE user_id = "30"
AND title = "137"
AND score >= 0
GROUP BY title
) pos
the problem is this: when both return values it work good, but when one return null, both shows null either if i run one it work true. for example if a person with id of 30 have 2 positive score in title1 and no negative score on title1 the query return null for neg_count and neg_sum and pos_sum and pos_count. but if i run only the part which work on positive it work true...
I tried the right join: it work only when second query has result and tried left join and it return value only when first have result. any better way to do this?
You can use RIGHT JOIN (I suspect mysql 5 or greater only, otherwise use left join and swap the sub-queries around). RIGHT JOIN insists on an ON clause but you can use a dummy IE:
SELECT *
FROM (
SELECT title,
count(*) cnt_neg,
sum(score) sum_neg
FROM scores_ofexpert
WHERE user_id = "30"
AND title = "137"
AND score < 0
GROUP BY title
) neg
RIGHT JOIN
(
SELECT count(*) cnt_pos,
sum(score) sum_pos
FROM scores_ofexpert
WHERE user_id = "30"
AND title = "137"
AND score >= 0
GROUP BY title
) pos
ON 1=1
It is working as expected. The documentation says:
INNER JOIN and , (comma) are semantically equivalent in the absence of
a join condition: both produce a Cartesian product between the
specified tables (that is, each and every row in the first table is
joined to each and every row in the second table).
So the first table with 0 rows matches 0 rows in second table and you get no results.
You may have to use a different join condition as required.
I found this answer. It worked well for me:
SELECT
SUM(CASE WHEN s.score < 0 THEN s.score ELSE 0 END) n_sum,COUNT(CASE WHEN s.score < 0 THEN s.score END) n_count,
SUM(CASE WHEN s.score >= 0 THEN s.score ELSE 0 END) p_sum,COUNT(CASE WHEN s.score >= 0 THEN s.score END) p_count,
SUM(s.score) `sum`
FROM scores_ofexpert s
WHERE s.user_id = '30' and title='135'
GROUP BY title
I have a statistical query that would return three rows (as I have 3 types by which I group by), I also know the order of the rows as I do explicit ORDER BY FIELD:
SELECT COUNT(id) AS c FROM Vehicles GROUP BY VehicleTypeID ORDER BY FIELD(VehicleTypeID, 1,2,3)
Is there a simple way to transpose the rows into columns? Something like (PSEUDO SQL):
SELECT c[0] AS CarsCount, c[1] AS MotorcyclesCount, c[2] AS TrucksCount FROM (
SELECT COUNT(id) AS c
FROM Vehicles
GROUP BY VehicleTypeID
ORDER BY FIELD(VehicleTypeID, 1,2,3)
)
Yes, it is a case statement with aggregation:
SELECT max(case when fieldnum = 1 then c end) AS CarsCount,
max(case when fieldnum = 2 then c end) AS MotorcyclesCount,
max(case when fieldnum = 3 then c end) AS TrucksCount
FROM (SELECT COUNT(id) AS c , FIELD(VehicleTypeID, 1,2,3) as fieldnum
FROM Vehicles
GROUP BY VehicleTypeID
) t;
Assume a simple case e.g. a table bug that has a column status that can be open,fixed etc.
If I want to know how many bugs are open I simply do:
select count(*) as open_bugs from bugs where status = 'open';
If I want to know how many bugs are open I simply do:
select count(*) as closed_bugs from bugs where status = 'closed';
If what want to know how many open and how many closed there are in a query that returns the results in 2 columns i.e.
Open | Closed|
60 180
What is the best way to do it? UNION concatenates the results so it is not what I want
This can be done by using a CASE expression with your aggregate function. This will convert the rows into columns:
select
sum(case when status = 'open' then 1 else 0 end) open_bugs,
sum(case when status = 'closed' then 1 else 0 end) closed_bugs
from bugs
This could also be written using your original queries:
select
max(case when status = 'open' then total end) open_bugs,
max(case when status = 'closed' then total end) closed_bugs
from
(
select status, count(*) as total from bugs where status = 'open' group by status
union all
select status, count(*) as total from bugs where status = 'closed' group by status
) d
Besides the CASE variants that aggregate over the whole table, there is another way. To use the queries you have and put them inside another SELECT:
SELECT
( SELECT COUNT(*) FROM bugs WHERE status = 'open') AS open_bugs,
( SELECT COUNT(*) FROM bugs WHERE status = 'closed') AS closed_bugs
FROM dual -- this line is optional
;
It has the advantage that you can wrap counts from different tables or joins in a single query.
There may also be differences in efficiency (worse or better). Test with your tables and indexes.
You can also use GROUP BY to get all the counts in separate rows (like the UNION you mention) and then use another aggregation to pivot the results in one row:
SELECT
MIN(CASE WHEN status = 'open' THEN cnt END) AS open_bugs,
MIN(CASE WHEN status = 'closed' THEN cnt END) AS closed_bugs
FROM
( SELECT status, COUNT(*) AS cnt
FROM bugs
WHERE status IN ('open', 'closed')
GROUP BY status
) AS g
Try this
select count(case when status = 'open' then 1 end) open_bugs,
count(case when status = 'closed' then 1 end) closed_bugs
from bugs