Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get maximum row of each group?

I have a database in which people submit votes for people in different places. At a given time, I want to find out who has the most votes at each place. (A person can be voted at two different places)

This is the SQL I have so far:

SELECT placeId, userVotedId, cnt 
FROM 
    (SELECT uvo.userVotedId, p.placeId, count(*) as cnt 
     FROM users as u, users_votes as uvo, places as p 
     WHERE u.userId = uvo.userVotedId 
       AND p.placeId = uvo.placeId 
     GROUP BY userVotedId, placeId) 
AS RESULT

which gives me this result:

enter image description here

Now, these are the rows I REALLY want:

enter image description here

What's missing in my query so I can get this?

  • I want one result per place. So I should see only distinct placeIds, with the userVotedId who received the most votes.

  • In the event of a tie, a random winner will do!

like image 273
Michael Eilers Smith Avatar asked Jan 25 '13 00:01

Michael Eilers Smith


People also ask

How do you find the maximum value of each group in SQL?

How do you get max for each group in SQL? To find the maximum value of a column, use the MAX() aggregate function; it takes a column name or an expression to find the maximum value. In our example, the subquery returns the highest number in the column grade (subquery: SELECT MAX(grade) FROM student ).

How do you find the maximum value of GROUP BY?

MySQL MAX() function with GROUP BY retrieves maximum value of an expression which has undergone a grouping operation (usually based upon one column or a list of comma-separated columns).

How do I get row wise max value in SQL?

Now we can move ahead to write our SQL query for finding maximum values in all the rows, This can be done using MAX(field) function in SQL.


1 Answers

Seems like you need one more aggregate. Use the MAX() aggregate on your cnt value and GROUP BY placeId, userVotedId:

SELECT placeId, userVotedId, max(cnt)
FROM 
(
  SELECT uvo.userVotedId, p.placeId, count(*) as cnt 
  FROM users as u
  INNER JOIN users_votes as uvo
    ON u.userId = uvo.userVotedId 
  INNER JOIN places as p 
    ON p.placeId = uvo.placeId 
  GROUP BY userVotedId, placeId
) AS RESULT
GROUP BY placeId, userVotedId

Note: I changed your query to use JOIN syntax instead of the commas between the tables.

Edit, based on your comment the following should work:

select total.uservotedid,
  total.placeid,
  total.cnt
from
(
  SELECT uvo.userVotedId, p.placeId, count(*) as cnt 
  FROM users as u
  INNER JOIN users_votes as uvo
    ON u.userId = uvo.userVotedId 
  INNER JOIN places as p 
    ON p.placeId = uvo.placeId 
  GROUP BY userVotedId, placeId
) total
inner join
(
  select max(cnt) Mx, placeid
  from
  (
    SELECT uvo.userVotedId, p.placeId, count(*) as cnt 
    FROM users as u
    INNER JOIN users_votes as uvo
      ON u.userId = uvo.userVotedId 
    INNER JOIN places as p 
      ON p.placeId = uvo.placeId 
    GROUP BY userVotedId, placeId
  ) mx
  group by placeid
) src
  on total.placeid = src.placeid
  and total.cnt = src.mx

See SQL Fiddle with Demo

The result is:

| USERVOTEDID | PLACEID | CNT |
-------------------------------
|          65 |      11 |   1 |
|          67 |      13 |   1 |
|          67 |      25 |   1 |
|          67 |      51 |   2 |

Edit #2, if you want a random number returned if there is a tie, then you can use user variables:

select uservotedid,
  placeid, 
  cnt
from
(
  select total.uservotedid,
    total.placeid,
    total.cnt,
    @rownum := case when @prev = total.placeid then @rownum+1 else 1 end rownum,
    @prev := total.placeid pplaceid
  from
  (
    SELECT uvo.userVotedId, p.placeId, count(*) as cnt 
    FROM users as u
    INNER JOIN users_votes as uvo
      ON u.userId = uvo.userVotedId 
    INNER JOIN places as p 
      ON p.placeId = uvo.placeId 
    GROUP BY userVotedId, placeId
  ) total
  inner join
  (
    select max(cnt) Mx, placeid
    from
    (
      SELECT uvo.userVotedId, p.placeId, count(*) as cnt 
      FROM users as u
      INNER JOIN users_votes as uvo
        ON u.userId = uvo.userVotedId 
      INNER JOIN places as p 
        ON p.placeId = uvo.placeId 
      GROUP BY userVotedId, placeId
    ) mx
    group by placeid
  ) src
    on total.placeid = src.placeid
    and total.cnt = src.mx
  order by total.placeid, total.uservotedid
) src
where rownum = 1
order by placeid, uservotedid

See SQL Fiddle with Demo

like image 142
Taryn Avatar answered Sep 21 '22 18:09

Taryn