MySQL - How to select rows with max value of a field - mysql

I have a table of users with their scores for each level of a game:
id | user_id | level | score
1 | David | 1 | 20
2 | John | 1 | 40
3 | John | 2 | 30
4 | Mark | 1 | 60
5 | David | 2 | 10
6 | David | 3 | 80
7 | Mark | 2 | 20
8 | John | 3 | 70
9 | David | 4 | 50
10 | John | 4 | 30
What is the SQL query needed to get for each level, who has the highest score?
The result should be:
id | user_id | level | score
4 | Mark | 1 | 60
3 | John | 2 | 30
6 | David | 3 | 80
9 | David | 4 | 50
Thank you

If you want to get ties, then you can do something like this:
select s.*
from scores s
where s.score = (select max(s2.score) from scores s2 where s2.level = s.level);
You could get one row per level by aggregating this:
select s.level, s.score, group_concat(s.user_id)
from scores s
where s.score = (select max(s2.score) from scores s2 where s2.level = s.level)
group by s.level, s.score;
This combines the users (if there is more than one) into a single field.

order by score desc in sub query, then select max(score) group by level.
select id, user_id , level , max(score) as score
from
(select * from scores order by score desc)A
group by level

If you only want the user, who reached the highest score first (no ties per level):
select *
from users u1
where id = (
select id
from users u2
where u2.level = u1.level
order by score desc, id asc
limit 1
)
You should have indexes (id) and (level, score, id)

Related

Multi-event tournament standings (with arbitrary number of entries)

Suppose you have a a multi-event competition where competitors can attempt any event an arbitrary number of times. (weird, I know.)
How do pull out a desired player's best time for each event,
and assign it a placing? (1st 2nd 3rd...)
Data example: Desired output:
Name | Event | Score Name | Event | Score | Rank
-------------------- ----------------------------
Bob 1 50 Given input: "Bob"
Bob 1 100 Bob 1 100 1
Bob 2 75 Bob 2 75 3
Bob 3 80 Bob 3 80 2
Bob 3 65
Given input: "Jill"
Jill 2 75 Jill 2 90 1
Jill 2 90 Jill 3 60 3
Jill 3 60
Given input: "Chris"
Chris 1 70 Chris 1 70 2
Chris 2 50 Chris 2 85 2
Chris 2 85 Chris 3 100 1
Chris 3 100
This is a build up of my previous question:
Multi-event tournament standings
I feel understand that problem much better (Thanks!), but I cannot bridge the gap to this version of the problem.
I have SQL 5.x so I cant use stuff like Rank(). This will also be crunching many thousands of scores.
Desired output can be acheaved with this query:
select
IF(event is NULL, CONCAT('Given input: "', name,'"'), name) as name,
IF(event is NULL, '', event) as event,
IF(event is NULL, '', max(score)) as score,
IF(event is NULL, '', (
select count(s2.name) + 1
from (
select name, max(score) as score
from scores es
where es.event = s.event
group by es.name
order by score desc
) s2
where s2.score > max(s.score)
)) as `rank`
from scores s
group by name, event with rollup
having name is not NULL
order by name, event;
And output (if run query in mysql cli):
+----------------------+-------+-------+------+
| name | event | score | rank |
+----------------------+-------+-------+------+
| Given input: "Bob" | | | |
| Bob | 1 | 100 | 1 |
| Bob | 2 | 75 | 3 |
| Bob | 3 | 80 | 2 |
| Given input: "Chris" | | | |
| Chris | 1 | 70 | 2 |
| Chris | 2 | 85 | 2 |
| Chris | 3 | 100 | 1 |
| Given input: "Jill" | | | |
| Jill | 2 | 90 | 1 |
| Jill | 3 | 60 | 3 |
+----------------------+-------+-------+------+
11 rows in set, 3 warnings (0.00 sec)
Should work on any Mysql 5.
You can get the highest score per event by an aggregation by event taking the max(). To simulate a dense_rank() you can use a subquery counting the scores higher than or equal to the current score per event.
For a particular contestant (here Bob) that makes:
SELECT d1.name,
d1.event,
max(d1.score) score,
(SELECT count(*)
FROM (SELECT d2.event,
max(d2.score) score
FROM data d2
GROUP BY d2.event,
d2.name) x1
WHERE x1.score >= max(d1.score)
AND x1.event = d1.event) rank
FROM data d1
WHERE d1.name = 'Bob'
GROUP BY d1.event
ORDER BY d1.event;
And for all of them at once:
SELECT d1.name,
d1.event,
max(d1.score) score,
(SELECT count(*)
FROM (SELECT d2.event,
max(d2.score) score
FROM data d2
GROUP BY d2.event,
d2.name) x1
WHERE x1.score >= max(d1.score)
AND x1.event = d1.event) rank
FROM data d1
GROUP BY d1.name,
d1.event
ORDER BY d1.name,
d1.event;
db<>fiddle
E.g.:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,name VARCHAR(12) NOT NULL
,event INT NOT NULL
,score INT NOT NULL
);
INSERT INTO my_table (name,event,score) VALUES
('Bob' ,1, 50),
('Bob' ,1,100),
('Bob' ,2, 75),
('Bob' ,3, 80),
('Bob' ,3, 65),
('Jill' ,2, 75),
('Jill' ,2, 90),
('Jill' ,3, 60),
('Chris',1, 70),
('Chris',2, 50),
('Chris',2, 85),
('Chris',3,100);
SELECT a.*
, FIND_IN_SET(a.score,b.scores) my_rank
FROM my_table a -- it's possible that this really needs to be a repeat of the subquery below, so
-- ( SELECT m.* FROM my_table m JOIN (SELECT name,event,MAX(score) score FROM my_table
-- GROUP BY name, event) n ON n.name = m.name AND n.event = m.event AND n.score = m.score) AS a
JOIN
(
SELECT x.event
, GROUP_CONCAT(DISTINCT x.score ORDER BY x.score DESC) scores
FROM my_table x
JOIN
( SELECT name
, event
, MAX(score) score
FROM my_table
GROUP
BY name
, event
) y
ON y.name = x.name
AND y.event = x.event
AND y.score = x.score
GROUP
BY x.event
) b
ON b.event = a.event
WHERE FIND_IN_SET(a.score,b.scores) >0;
+----+-------+-------+-------+------+
| id | name | event | score | rank |
+----+-------+-------+-------+------+
| 2 | Bob | 1 | 100 | 1 |
| 3 | Bob | 2 | 75 | 3 |
| 4 | Bob | 3 | 80 | 2 |
| 6 | Jill | 2 | 75 | 3 |
| 7 | Jill | 2 | 90 | 1 |
| 8 | Jill | 3 | 60 | 3 |
| 9 | Chris | 1 | 70 | 2 |
| 11 | Chris | 2 | 85 | 2 |
| 12 | Chris | 3 | 100 | 1 |
+----+-------+-------+-------+------+

MySQL - Count number of rows in each group

I have a SQL table user_game which contains the games that a user owns:
| id | user_id | game_id |
|----|---------|---------|
| 83 | 1 | 1 |
| 84 | 1 | 2 |
| 85 | 1 | 3 |
| 86 | 2 | 2 |
| 87 | 2 | 3 |
| 88 | 2 | 4 |
| 89 | 3 | 2 |
I am trying to count the number of users which have 1 game, 2 games, 3 games.. etc.
User 1 has 3 games, User 2 has 3 games, and User 3 has 1 game. Therefore these are the results I want to achieve:
| no_of_games | COUNT(no_of_games) |
|-------------|--------------------|
| 1 | 1 |
| 2 | 0 |
| 3 | 2 |
COUNT(no_of_games) is the number of users that have that number of games.
I can individually get the number of users for each no_of_games with this query:
-- Select no. of users with 1 game
SELECT no_of_games, COUNT(no_of_games)
FROM
(
-- Select no. of games each user has
SELECT user_id, COUNT(1) as no_of_games
FROM user_game
GROUP BY user_id
) as A
WHERE no_of_games = 1;
which gives the results:
| no_of_games | COUNT(no_of_games) |
|-------------|--------------------|
| 1 | 1 |
However I have to change the no_of_games = 1 to 2, 3, 4... manually and UNION them with this solution and I can't do it for ~60 cases.
Is there a simpler way to achieve this?
Your problem is a bit tricky, because groups of games which do not appear in your data with a certain frequency (e.g. 2) will not appear in the result set just using your original table. In the query below, I use a second table called nums which simply contains the sequence 1 through 10 representing counts of number of games. By using a LEFT JOIN we can retain each game count in the final result set.
SELECT t1.no_of_games,
COALESCE(t2.no_of_games_count, 0) AS no_of_games_count
FROM nums t1
LEFT JOIN
(
SELECT t.no_of_games, COUNT(*) AS no_of_games_count
FROM
(
SELECT COUNT(*) AS no_of_games
FROM user_game
GROUP BY user_id
) t
GROUP BY t.no_of_games
) t2
ON t1.no_of_games = t2.no_of_games
ORDER BY t1.no_of_games
And here is the definition I used for nums:
CREATE TABLE nums (`no_of_games` int);
INSERT INTO nums (`no_of_games`)
VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
Demo here:
SQLFiddle
You can find count of games for each user and then find count of users for each count of games.
select cnt no_of_games, count(*) cnt_no_of_games
from(
select user_id, count(*) cnt
from your_table
group by user_id
) t group by cnt;

Fetch first N rows including tie values MYSQL

+-----+-------+-----+
| id | Name |Votes|
+-----+-------+-----+
| 1 | Joe | 36 |
| 2 | John | 34 |
| 3 | Mark | 42 |
| 4 | Ryan | 29 |
| 5 | Jay | 36 |
| 6 | Shawn | 39 |
+-----+-------+-----+
For this example, what I want is to retrieve the rows with the first 3 highest votes. However, if you'll notice, there are two rows with the same vote count. So this should be the result:
+-----+-------+-----+
| id | Name |Votes|
+-----+-------+-----+
| 3 | Mark | 42 |
| 6 | Shawn | 39 |
| 1 | Joe | 36 |
| 5 | Jay | 36 |
+-----+-------+-----+
How to achieve this?
You will have to perform an INNER JOIN, using the table back on itself. First, you want to select the top 3 unique/distinct scores, and this can be done by using:
SELECT DISTINCT Votes FROM mytable ORDER BY Votes DESC LIMIT 3
Now that you have obtained the top 3 scores, you want to join it back to the original table:
SELECT t1.* FROM mytable AS t1
INNER JOIN
(SELECT DISTINCT Votes FROM mytable ORDER BY Votes DESC LIMIT 3) AS topvotes
ON
topvotes.Votes = t1.Votes
ORDER BY t1.Votes DESC
Refer to a simple diagram for the strategy:
For this query to be efficient, you will want to index the Votes column so that the subquery can fish out distinct votes quickly ;)
Here is a proof-of-concept SQLfiddle: http://sqlfiddle.com/#!9/c78f0/10
Probably not the most efficient, but I think this should work:
SELECT * FROM scores WHERE score IN(SELECT score FROM scores ORDER BY score DESC LIMIT 3)
Although this can yield an error about limit not being supported in subqueries.
A workaround;
SELECT * FROM scores WHERE score IN(SELECT * FROM (SELECT score FROM scores ORDER BY score DESC LIMIT 3) AS t)

How to obtain top 5 players for each game from a MySQL high score board?

I'm trying to setup a high score board to be displayed in my arcade game room. My target is to display the top 5 players for each game from the table GAME.
I've created three tables:
Table GAME :
+----------+--------------+
| id_game | name_game |
+----------+--------------+
| 1 | PAC MAN |
| 2 | GALAXIAN |
| 3 | XEVIOUS |
table PLAYER :
+----------+---------------+
| id_player | name_player |
+----------+---------------+
| 1 | BRUNO |
| 2 | STEVE |
table SCORE :
+----------+----------+-----------+------+------+
| id_score | id_game | id_player | date | score |
+----------+----------+-----------+------+------+
| 1 | 1 | 1 | 01/10/15 | 230234 |
| 2 | 1 | 2 | 04/10/15 | 120234 |
| 3 | 1 | 1 | 03/10/15 | 440224 |
| 4 | 1 | 1 | 06/10/15 | 200000 |
| 5 | 1 | 2 | 09/10/15 | 330233 |
| 6 | 1 | 1 | 01/10/15 | 510000 |
| 7 | 2 | 1 | 01/10/15 | 730874 |
| 8 | 2 | 2 | 01/10/15 | 990900 |
| 9 | 3 | 1 | 01/10/15 | 444000 |
I would like some help to build the sql code to produce something like this :
PAC MAN
1. BRUNO 510000 PTS 01/10/15
2. STEVE 330233 PTS 09/10/15
3. XXXXX 230233 PTS 02/10/14
4. YYYYY 130233 PTS 06/10/15
5. ZZZZZ 030233 PTS 10/10/13
GALAXIAN
1. STEVE 990900 PTS 01/10/15
2. BRUNO 730874 PTS 01/10/15
3. XXXXX 230233 PTS 02/10/14
4. YYYYY 130233 PTS 06/10/15
5. ZZZZZ 030233 PTS 10/10/13
If the same player did more than one score on the same game, only his/her best score should be displayed. Can you help me to set up the code for doing this?
SQL fiddle
SELECT MAX(s.score) AS score, p.name_player, g.name_game
FROM GAME g
JOIN SCORE s ON s.id_game = g.id_game
JOIN PLAYER p ON p.id_player = s.id_player
GROUP BY p.id_player, g.id_game
ORDER BY g.id_game, score
Well, I still have an issue with your query above :-(
The query displays well the best score of per unique player, but the date associated is not correct. The 'date' field still gives the first date from the first score done per player.
Let me illustrate this. I just add the DATE value to the first line of your query, which gives :
SELECT MAX(s.score) AS score, p.name_player, g.name_game, date
FROM GAME g
JOIN SCORE s ON s.id_game = g.id_game
JOIN PLAYER p ON p.id_player = s.id_player
GROUP BY p.id_player, g.id_game
ORDER BY g.id_game, score
+----------+----------+-----------+------+------+
| id_score | id_game | id_player | date | score |
+----------+----------+-----------+------+------+
| 1 | 1 | 1 | 01/10/12 | 230234 |
| 2 | 1 | 1 | 04/10/13 | 120234 |
| 3 | 1 | 1 | 03/10/14 | 440224 |
Your query will return this :
440224 1 1 01/10/12
But I'm expecting this : 440224 1 1 03/10/14
I tried to add max(date) here in there but I guess there is obviously something more complex to do?
You would want something like this. The first query is probably the one you would actually use to run it against your own tables. The second example you can run directly in mysql and fudge the data as required.
select
g.name_game,
p.name_player,
max(s.score),
s.date
from
game g
inner join score s on
s.id_game = g.id_game
inner join player p on
p.id_player = s.id_player
where
g.name_game = 'PAC MAN'
group by
g.name_game,
p.name_player
ORDER BY
s.score asc
LIMIT
5;
select
g.name_game,
p.name_player,
max(s.score),
s.date
from
(
select 1 id_game, 'PAC MAN' name_game union all
select 2 id_game, 'GALAXIAN' name_game union all
select 3 id_game, 'XEVIOUS' name_game
) g
inner join (
select 1 id_score, 1 id_game, 1 id_player, '01/10/15' date, 230234 score union all
select 2 id_score, 1 id_game, 2 id_player, '04/10/15' date, 120234 score union all
select 3 id_score, 1 id_game, 1 id_player, '03/10/15' date, 440224 score union all
select 4 id_score, 1 id_game, 1 id_player, '06/10/15' date, 200000 score union all
select 5 id_score, 1 id_game, 2 id_player, '09/10/15' date, 330233 score union all
select 6 id_score, 1 id_game, 1 id_player, '01/10/16' date, 410000 score union all
select 6 id_score, 1 id_game, 1 id_player, '01/10/15' date, 510000 score union all
select 7 id_score, 2 id_game, 1 id_player, '01/10/15' date, 730874 score union all
select 8 id_score, 2 id_game, 2 id_player, '01/10/15' date, 990900 score union all
select 9 id_score, 3 id_game, 1 id_player, '01/10/15' date, 444000 score
) s on
s.id_game = g.id_game
inner join (
select '1' id_player, 'BRUNO' name_player union all
select '2' id_player, 'STEVE' name_player
) p on
p.id_player = s.id_player
where
g.name_game = 'PAC MAN'
group by
g.name_game,
p.name_player
ORDER BY
s.score asc
LIMIT
5;
Maybe next time post what you have tried?
You could also use a group by if you wanted them all in the same query. Just replace [game_name] with the name of the game you want.

MySQL group by with MAX not working as expected?

I have a table:
ID | User | Amount
1 | 1 | 50
2 | 1 | 80
3 | 2 | 80
4 | 2 | 100
5 | 1 | 90
6 | 1 | 120
7 | 2 | 120
8 | 1 | 150
9 | 2 | 300
I do a query:
SELECT * FROM TABLE ORDER BY amount DESC group by userid
I'm getting this:
ID | User | Amount
1 | 1 | 50
2 | 1 | 80
But I was expecting:
ID | User | Amount
9 | 2 | 300
8 | 1 | 150
What is wrong with my sql?
When grouping you have to use aggregate functions like max() for all columns that are not grouped by
select t.*
from table t
inner join
(
SELECT userid, max(amount) as total
FROM TABLE
group by userid
) x on x.userid = t.userid and x.total = t.amount
ORDER BY t.amount DESC
Another solution.Check SQL Fiddle
Using FIND_IN_SET clause
SELECT
ua.*
FROM user_amount ua
WHERE FIND_IN_SET(ua.amount,(SELECT
MAX(ua1.amount)
FROM user_amount ua1
WHERE ua1.user = ua.user)) > 0
ORDER BY amount desc;
Using IN clause
SELECT
ua.*
FROM user_amount ua
WHERE ua.amount IN (SELECT
MAX(ua1.amount)
FROM user_amount ua1
WHERE ua1.user = ua.user)
ORDER BY amount desc