MYSQL - count how many times a person comes second in a competition - mysql

I have the following table called 'players' which contains scores from 3 games played by Tim, Bob and Jon.
playid | name | score
1 | Tim | 10
1 | Bob | 5
2 | Tim | 5
2 | Bob | 10
3 | Tim | 5
3 | Bob | 10
3 | Jon | 4
I want to be able to count the number of times that Tim, Bob and Jon have come second i.e. Tim = 2, Bob = 1, Jon = 0.
I have the following query:
SELECT name FROM players WHERE playid = 1 ORDER BY score Desc LIMIT 1, 1
Which returns the name of the person in second place in the first game i.e. Bob, but I can't figure out how to extend this to cover all games and players. Eventually I also want to be able to count the number of times they come 3rd, 4th etc.
Thanks in advance

Try with following one:
SELECT count(playid), name, score
FROM `players`
WHERE score = (SELECT MAX(score) FROM players WHERE score < (SELECT MAX(score) FROM players))
GROUP BY score, name;

With multiple joins and groupings:
select pl.name, ifnull(counter, 0) counter from (
select distinct name from players) pl
left join (
select players.name, count(*) counter from players
inner join (
select p.playid, max(p.score) as secondscore from (
select players.* from players
left join (
select playid, max(score) as maxscore
from players
group by playid) p
on p.playid = players.playid and p.maxscore = players.score
where p.maxscore is null) p
group by p.playid
) p
on p.playid = players.playid and p.secondscore = players.score
group by players.name) p
on p.name = pl.name
See the demo

You can use this query below to find the secondly-ranked people :
SELECT q2.playid, q2.name
FROM
(
SELECT q1.* , if(switch1,#r:=#r+1,#r:=0) as switch2
FROM
(
SELECT playid, score, name,
if(playid=#p,#p:=0,#p:=playid+1) as switch1
FROM players
JOIN ( SELECT #p:=0, #r:=-1 ) p2
ORDER BY playid, score desc
) q1
) q2
WHERE q2.switch2 = 1
ORDER BY q2.playid
playid name
------ ----
1 Bob
2 Tim
3 Tim
4 George
Rextester Demo

As per the comment by Raymond Nijland, in MySQL 8.0+ you can use window functions to achieve this:
SELECT name, COUNT(*) AS second_place_count
FROM (
SELECT
name,
playid,
ROW_NUMBER() OVER (PARTITION BY playid ORDER BY score DESC) AS rn
FROM players
) AS ranks
WHERE ranks.rn = 2
GROUP BY name
...or if you want to extend this to all places:
SELECT
name,
rn AS place,
COUNT(*) AS place_count
FROM (
SELECT
name,
playid,
ROW_NUMBER() OVER (PARTITION BY playid ORDER BY score DESC) AS rn
FROM players
) AS ranks
GROUP BY
name,
rn

Related

How to retrieve number of records from mysql database in a group?

I have a table with multiple records in it for full name and surname.
---------------------------
id | name | lastname
---------------------------
1 | A smith | smith
2 | B smith | smith
3 | c smith | smith
4 | A josh | josh
5 | B josh | josh
6 | C josh | josh
7 | D josh | josh
8 | A white | white
9 | D white | white
10| z white | white
And so so....more than 100k records. Now what i want to do is to retrieve latest 7 records for each surname up to 9 surnames. I have 500 surnames but i just want latest 9 surnames.. In my application "latest" means "largest value of id column."
This is the command that i tried to make but when i execute it. i am not getting any response from server. this is happening because of database size and my command is taking a lot of time. its just keep me waiting:
SELECT * FROM `queue` s WHERE ( SELECT COUNT(*) FROM `queue` f WHERE f.lastname = s.lastname AND f.id >= s.id LIMIT 0 , 7) <=7
Can someone suggest me better way of retrieving my goal.
Let's build this up from the basics.
Your first step is to create a subquery to get the latest nine surnames (http://sqlfiddle.com/#!9/aee62e/19/0). By that I mean the surnames with the highest id values.
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
And, in MySQL, that was the easy part. Now you need to retrieve the seven highest ranked (largest id) rows for each selected surname. As a start, you could do this to get all records for the selected surnames, in descending order by id. (http://sqlfiddle.com/#!9/aee62e/18/0).
SELECT t.*, namerank
FROM t
JOIN (
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
) h ON t.lastname = h.lastname
ORDER BY t.lastname, t.id DESC
This is correct, but contains too many rows. Next we need to get the ranking for each lastname's rows. A lower ranking means a higher id value. This is the nasty hack in MySQL. (Nasty because it mixes procedural operations on local variables with the inherently declarative nature of SQL.) (http://sqlfiddle.com/#!9/aee62e/17/0)
SELECT IF(detail.lastname = #prev_lastname, #rank := #rank+1, #rank :=1) rank,
namerank,
#prev_lastname := detail.lastname lastname,
id,
name
FROM (
SELECT t.*, namerank
FROM t
JOIN (
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
) h ON t.lastname = h.lastname
ORDER BY t.lastname, t.id DESC
) detail
JOIN (SELECT #rank := 0, #prev_lastname := '') initializer
Finally we need to wrap that whole mess in an outer query to pick off the seven highest ranked rows for each lastname value. (http://sqlfiddle.com/#!9/aee62e/16/0)
SELECT *
FROM (
SELECT IF(detail.lastname = #prev_lastname, #rank := #rank+1, #rank :=1) rank,
namerank,
#prev_lastname := detail.lastname lastname,
id,
name
FROM (
SELECT t.*, namerank
FROM t
JOIN (
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
) h ON t.lastname = h.lastname
ORDER BY t.lastname, t.id DESC
) detail
JOIN (SELECT #rank := 0, #prev_lastname := '') initializer
) ranked
WHERE rank <= 7
ORDER BY namerank DESC, rank
I believe the technical term for the complexity of your requirement and this solution is "hairball." It definitely puts the structured in Structured Query Language.

ORDER BY after GROUP BY does not working

SELECT *
FROM (SELECT id, user, MAX(score) FROM table_1 GROUP BY user) AS sub
ORDER BY 'sub.score' ASC;
This SQL query should select from a table only a score per user, and for accuracy, the highest.
The table structure is this:
+-----------------------+
| id | score | username |
+-----------------------+
| 1 | 15 | mike |
| 2 | 23 | tom |
| 3 | 16 | mike |
| 4 | 22 | jack |
etc..
The result should be like:
3 mike 16
2 tom 23
4 jack 22
And then reordered:
3 mike 16
4 jack 22
2 tom 23
But the query does not reorder the subquery by score. How to do so?
Let's look at what you are doing step by step:
SELECT id, user, MAX(score) FROM table_1 GROUP BY user
Here you are grouping by user name, so you get one result row per user name. In this result row you select the user name, the maximum score found for this user name (which is 16 for 'mike') and one of the IDs found for the user name (which can be 1 or 3 for 'mike', the DBMS is free to choose one). This is probably not what you want.
SELECT * FROM (...) AS sub ORDER BY 'sub.score' ASC;
'sub.score' is a string (single quotes). You want to order by the max score from your subquery instead. So first give the max(score) a name, e.g. max(score) as max_score, and then access that: ORDER BY sub.max_score ASC.
Anyway, if you want the record with the maximum score for a user name (so as to get the according ID, too), you could look for records for which not exists a record with the same user name and a higher score. Sorting is easy then: as there is no aggregation, you simply order by score:
select * from table_1 t1 where not exists
(select * from table_1 higher where higher.name = t1.name and higher.score > t1.score)
order by score;
Assuming user|score is unique..:
SELECT x.*
FROM table_1 x
JOIN ( SELECT user, MAX(score) score FROM table_1 GROUP BY user) y
ON y.user = x.user
AND y.score = x.score
ORDER BY x.score
No need to write sub queries.
Simply you can use this way:
SELECT id, `user`, MAX(score) FROM table_1 GROUP BY `user`
ORDER BY MAX(score);
If you want query with sub query:
SELECT * FROM (SELECT id, `user`, MAX(score) as max_score FROM table_1
GROUP BY `user`) AS sub ORDER BY max_score;

SQL filter rows without join

I'm always "irk" by unnecessary join. But in this case, I wonder if it's possible to not use join.
This is an example of the table I have:
id | team | score
1 | 1 | 300
2 | 1 | 257
3 | 2 | 127
4 | 2 | 533
5 | 3 | 459
This is what I want:
team | score | id
1 | 300 | 1
2 | 533 | 4
3 | 459 | 5
Doing a query looking like this:
(basically: who's the best player of each team)
SELECT team, MAX(score) AS score, id
FROM my_table
GROUP BY team
But I get something like that:
team | score | id
1 | 300 | 1
2 | 533 | 3
3 | 459 | 5
But it's not the third player that got 533 points, so the result have no consistency.
Is it possible to get truthworthy results without joining the table with itself? How to achieve that?
You can do it without joins by using subquery like this:
SELECT id, team, score
FROM table1 a
WHERE score = (SELECT MAX(score) FROM table1 b WHERE a.team = b.team);
However in big tables this can be very slow as you have to run the whole subquery for every row in your table.
However there's nothing wrong with using join to filter results like this:
SELECT id, team, score FROM table1 a
INNER JOIN (
SELECT MAX(score) score, team
FROM table1
GROUP BY team
) b ON a.score = b.score AND a.team = b.team
Although joining itself is quite expensive, this way you only have to run two actual queries regardless how many rows are in your tables. So in big tables this method can still be hundreds, if not thousands of times faster than the first method with subquery.
You can use variables:
SELECT id, team, score
FROM (
SELECT id, team, score,
#seq := IF(#t = team, #seq,
IF(#t := team, #seq + 1, #seq + 1)) AS seq,
#grp := IF(#t2 = team, #grp + 1,
IF(#t2 := team, 1, 1)) AS grp
FROM mytable
CROSS JOIN (SELECT #seq := 0, #t := 0, #grp := 0, #t2 := 0) AS vars
ORDER BY score DESC) AS t
WHERE seq <= 3 AND grp = 1
Variable #seq is incremented each time a new team is met as the records are being processed in descending score order. Variable #grp is used to enumerate records within each team partition. Records with #grp = 1 are the ones having the greatest score value within the team slice.
Demo here
Unfortantly , MySQL doesn't support window functions like ROW_NUMBER() which could have solved this easily.
There are several ways on doing that:
NOT EXISTS() :
SELECT * FROM YourTable t
WHERE NOT EXISTS(SELECT 1 FROM YourTable s
WHERE t.team = s.team AND s.score > t.score)
NOT IN() :
SELECT * FROM YourTable t
WHERE (t.team,t.score) IN(SELECT s.team,MAX(s.score)
FROM YourTable s
GROUP BY s.team)
A correlated query:
SELECT distinct t.id,t.team,
(SELECT s.score FROM YourTable s
WHERE s.team = t.team
ORDER BY s.score DESC
LIMIT 1)
FROM YourTable t
Or a join which I understand you already have.
EDIT : I take my words back, you can do it with a variable like #GiorgosBetsos solution.
You could do something like this:
SELECT team, score, id
FROM (SELECT *
,RANK() OVER
(PARTITION BY team ORDER BY score DESC) AS Rank
FROM my_table) ranked_result
WHERE Rank = 1;
Some info on Rank functionality: Clicketyclickclick

How to get the rank of a row in mysql query

this my database structure
table : players
id | name | score
1 | Bob | 600
2 | Alex | 1400
3 | John | 800
4 | sara | 2000
I need to select john's row and count what is the john' rank OrderBy score
as you see john is 3rd (800) , sara is 1st (2000), Alex is 2nd (1400) in score ranks
Select #rownum := #rownum + 1 AS rank form players where id=3 OrderBy score
any idea ?
You can do it by a subquery and count the players who has score more than the score of a certian id
Select count(*) as rank
from players
where score > (select score from players where id=3)
But if you want to have other information beside the rank you can do it by
SELECT ranks . *
FROM (
SELECT #rownum := #rownum +1 ‘rank’, p.id, p.score
FROM players p, (SELECT #rownum :=0)r
ORDER BY score DESC
) ranks
WHERE id =3
select rank
from
(
Select id, name, #rownum := #rownum + 1 AS rank
from players
cross join (select #rownum := 0) r
Order By score desc
) tmp
where id = 3
Might be easier to do a self join, where the joined table score is greater (to get the rows with a higher score) and just do a count:-
SELECT COUNT(*)
FROM players a
INNER JOIN players b
ON a.score >= b.score
WHERE a.id = 3
Question is what to do with equal scores.

Order by two columns

Imagine the following MySQL table of orders:
id | name
1 | Mike
2 | Steve
3 | Janet
4 | Juliet
5 | Mike
6 | Jane
This is my current query:
SELECT * FROM table ORDER BY id DESC
However, I'd like to "group" those by name, so that I have orders from the same person listed after one another, however, I cannot do ORDER BY name.
This is my desired output:
id | name
6 | Jane
5 | Mike
1 | Mike
4 | Juliet
3 | Janet
2 | Steve
What's the query for this output?
E.g.:
SELECT y.id
, y.name
FROM my_table x
JOIN my_table y
ON y.name = x.name
GROUP
BY name
, id
ORDER
BY MAX(x.id) DESC
, id DESC;
You need to have special calculation to get their row position.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT Name,
#ord := #ord + 1 ord
FROM
(
SELECT MAX(ID) ID, NAME
FROM TableName
GROUP BY Name
) a, (SELECT #ord := 0) b
ORDER BY ID DESC
) b ON a.Name = b.Name
ORDER BY b.ord, a.ID DESC
SQLFiddle Demo
You can do it via double ORDER BY:
SELECT * FROM t ORDER BY name ASC, id DESC
SELECT * FROM table1
ORDER BY field(NAME,'Mike','Jane') desc,
`ID` desc;
exactly as you asked
You could also try this query if you want to have something which is more generic SQL.
SELECT id, name
FROM ( SELECT id, name, (SELECT MAX(id) from Table1 where name=t.name) AS max_id
FROM Table1 AS t
ORDER BY max_id DESC, id DESC) as x
What about to use group by?
You can group it by name and then order...