sql and getting elements around a specific one - mysql

My sql database stores the highscores for the players of a game like this:
playerName
scores
To show the scores I'm trying to create a query that returns for a playerId "joe" the positon of joe as well as one player with more and less points than joe:
(rank) (playerName) (scores)
5000 luci 2001
5001 joe 1900
5002 marc 1750
(I added the rank here just to make it more clear. It isn't stored and represents just the position in the list when sorting by the scores)
Is there a way to do soemthing like this with a query without getting half of the database as result?

You'd ideally want to be using window functions to do this, namely row_number(), lead() and lag().
These are not available in MySQL, but here are a few workarounds (also row_number(), picked up from the same author).
Using window functions:
with
players as (
select row_number() over w as rank,
playername,
scores
from players
window w (order by scores desc)
),
player as (
select rank
from players
where playername = 'joe'
)
select rank,
playername,
scores
from players
join player on players.rank between player.rank - 1 and player.rank + 1
Alternatively (slightly faster):
with
player_range as (
select scores - 100 as scores -- or whatever is large enough based on your stats
from players
where playername = 'joe'
),
players as (
select row_number() over w as rank,
players.playername,
players.scores
from players
join player_range
on player_range.scores <= players.scores
window w (order by scores desc)
),
player as (
select rank
from players
where playername = 'joe'
)
select rank,
playername,
scores
from players
join player on players.rank between player.rank - 1 and player.rank + 1

To determine joe's rank you have to query the db and order by scores.

Related

How can I select the value of a column depending on where the maximum value between a second and third column is, in sql?

I am a first year student learning SQL. I would like to know how I can select the data_jogo (date_game) value, where a certain player achieved his maximum score, in a match.
This player can appear as player 1 (id_jogador1) or player 2 (id_jogador2).
Table name is "partidas", translating it means matches, and
"pontuacao_jog1" means player 1's score, and "pontuacao_jog2" is the same for player 2.
enter image description here
I tried this way, but doesn't work (#1111 - Uso inválido de função de agrupamento (GROUP)):
SELECT partidas.data_jogo AS Date
FROM partidas
WHERE (partidas.id_jogador1 = 'CR7' OR partidas.id_jogador2 = 'CR7')
AND GREATEST (max (partidas.pontuacao_jog1), max (partidas.pontuacao_jog2));
Can someone help please?
Thank you, João
You can use UNION ALL to get a player's scores in an easy way. Then you can order the scores and use LIMIT to get the row with the maximum score:
WITH scores AS
(
SELECT pontuacao_jog1 AS score, data_jogo
FROM partidas
WHERE id_jogador1 = 'CR7'
UNION ALL
SELECT pontuacao_jog2 AS score, data_jogo
FROM partidas
WHERE id_jogador2 = 'CR7'
)
SELECT *
FROM scores
ORDER BY score DESC
LIMIT 1;
Only problem: If there are two days with the same maximum score, you'll only show one of them arbitrarily chosen. If you want to show both:
WITH scores AS ( <same as above> )
SELECT *
FROM scores
WHERE score = (SELECT MAX(score) FROM scores);
And here is an alternative way to write the CTE (aka WITH clause):
WITH scores AS
(
SELECT
CASE WHEN id_jogador1 = 'CR7'
THEN pontuacao_jog1
ELSE pontuacao_jog2
END AS score,
data_jogo
FROM partidas
WHERE (id_jogador1 = 'CR7' OR id_jogador2 = 'CR7')
)

Make a leaderboard ranking from table in sql

I have a database with names and their scores and I'd like to insert into the table the rank according to the score. My database looks a bit like this:
phpmyadmin database
I would like to make it so when the score is higher than a rank the ranks change in the database. So if there were a player 1 with 500 points and a player 2 with 1500 points, the code would insert the rank into the rank column corresponding with the player's name.
Like this:
Rank
Name
Score
1
Player2
1500
2
Player1
500
Don't store the RANK in the database calculate it on the fly when you read the data using the RANK() function.
SELECT
Player,
Score,
RANK() OVER(ORDER BY Score DESC) AS 'RANK'
FROM
PlayerScore
ORDER BY
Score DESC
If you can have tied scores you can use DENSE_RANK(). This would give you ranks of 1,2,2,3. RANK would just give you 1,2,2,4

Select rank of a specific player sql query

I'm trying to make a query and I already searched for an answer on stackof but didn't find one matching my needs.
I have a table named player in which there are two columns, "nickname" and "score".
I use this query to get the top 5 players:
SELECT nickname, score
FROM player
ORDER BY score DESC LIMIT 5;
and I got this as the answer:
nickname - score:
zod - 30
ciao - 20
jiji - 20
mayina - 20
jon - 0.
Now, I'd like to have the rank of a single player, let's say "jiji" and get 3 as a result, because it's the third result in the list.
I tried many queries like
SELECT COUNT(*) AS rank
FROM player
WHERE score >= (SELECT score FROM player WHERE nickname = 'jiji')
but they always return 4 for "jiji" or "ciao", which is the rank of the last player who gets 20 as score in that table.
How can I get to have 3 for "jiji", instead? Thank you very much.
Try this:
SET #rank=0;
SELECT *
FROM (SELECT #rank:=#rank+1, nickname, score
FROM player
ORDER BY score
DESC) AS t
WHERE t.nickname = 'jiji';
Correct comment about this not being stable in case of score ties. To make it stable, we can change the sorting to be based on score and then nickname:
SELECT *
FROM (SELECT #rank:=#rank+1, nickname, score
FROM player
ORDER BY score, nickname
DESC) AS t
WHERE t.nickname = 'jiji';
Using commonly used definitions, the rank for jiji would be:
SELECT count(*) + 1 AS rank
FROM player
WHERE score > (SELECT score FROM player WHERE nickname = 'jiji');
This returns "2", because there are ties when score = 30.
If you want the rank to be stable and different for each row, you need an additional key. An obvious key (in this case) is nickname:
SELECT count(*) AS rank
FROM player p CROSS JOIN
(SELECT score FROM player WHERE nickname = 'jiji') s
WHERE p.score > s.score or
(p.score = s.score and p.nickname <= 'jiji');

Mysql return matching users in subqueries

I'm looking for a way to write one query to compare the results of multiple mysql subqueries, and return users that are in each query.
I have a that contains fantasy football stats for players. To simplify, in this case there are 3 columns I'm using: player, points, year.
I'm looking to run a query that returns a list of players who finished in the top 50 (based on points) in both 2010 and 2011.
I've done lots of searching around on playing with subqueries, doing joins on one table, etc but am still coming up at a loss on how to approach this.
You can do something like this:
SELECT a.player
FROM (SELECT player FROM players WHERE Year = 2010 ORDER BY points DESC LIMIT 50) a
JOIN
(SELECT player FROM players WHERE Year = 2011 ORDER BY points DESC LIMIT 50) b
ON a.player = b.player
Here is an example. I assumed that you calculate top50 based on sum of points and you have several entries for each player in each year.
select y2010.player
from (
select player, sum from (
select st1.player player, sum(st1.points) sum from stats st1 where st1.year = 2010 group by st1.player order by sum desc
) t1 limit 50 offset 0
) y2010, (
select player, sum from (
select st1.player player, sum(st1.points) sum from stats st1 where st1.year = 2011 group by st1.player order by sum desc
) t1 limit 50 offset 0
) y2011
where y2010.player = y2011.player
You can use a UNION ALL, this will get you the Top 50 in both years and put them in the same result set, no joining required:
(
select player, year, points
from players
where year = 2010
order by points desc
limit 50
)
union all
(
select player, year, points
from players
where year = 2011
order by points desc
limit 50
);
It's slightly ambiguous whether you want:
all players who finished in the top 50 in 2010, as well as all players who finished in the top 50 in 2011:
SELECT *
FROM scores
WHERE year = 2010
AND points >= (SELECT MIN(points) FROM (
SELECT points
FROM scores
WHERE year = 2010
ORDER BY points DESC
LIMIT 50
) t)
UNION ALL
SELECT *
FROM scores
WHERE year = 2011
AND points >= (SELECT MIN(points) FROM (
SELECT points
FROM scores
WHERE year = 2011
ORDER BY points DESC
LIMIT 50
) t)
all players who finished in the top 50 in both 2010 and 2011, in which case you'll need to further group the results:
SELECT player
FROM (
-- query as above
) t
GROUP BY player
HAVING COUNT(DISTINCT year) = 2

SQL query to return the three highest values for one column "grouped" by another column

Let's say I have a table like this:
Player Score
A 5
B 4
A 3
B 2
A 1
B 1
A 2
B 3
A 4
B 5
I need an SQL query that will return the three highest scores per player in descending order "grouped" by player i.e.
Player Score
A 5
A 4
A 3
B 5
B 4
B 3
Very grateful for any pointers.
This is old-fashioned (read: basic sql) way of producing top-n per group. You might join the table to itself on group condition (here it is player) and pick records with higher score on right side; if there are three or less such records, the row is one of top n rows per group.
select player.player, player.score
from Player
left join Player p2
on p2.player = player.player
and p2.score > player.score
group by player.player, player.score
having count(distinct p2.score) < 3
order by 1, 2 desc
Alternative version you might check, using not exists:
select player, score
from player
where not exists
(
select p2.player
from Player p2
where p2.player = player.player
and p2.score > player.score
group by p2.player
having count(distinct p2.score) > 3
)
order by 1, 2 desc
This two versions differ in presentation of ties - while first one returns one row (by nature of group by) and needs to be joined back to original table to show all records, second one works directly from original table showing all data and ties at once.
You can find Demo at Sql Fiddle.
in SQL server:
select p.player, p.score
from PS p
where p.score in (select top 3 score from PS
where player = p.player order by score desc)
order by p.player asc, p.score desc
in MySql:
select p.player, p.score
from PS p
where p.score in (select score from PS
where player = p.player order by score desc limit 3)
order by p.player asc, p.score desc
I think what you are looking for can be found here:
http://www.sql-ex.ru/help/select16.php
Basically, the best solution uses the RANK function. Here is the example code from the site:
SELECT maker, model, type FROM
(
SELECT maker, model, type, RANK() OVER(PARTITION BY type ORDER BY model) num
FROM Product
) X
WHERE num <= 3
You would just need to modify the Partition By section to order by your score in descending order.
EDIT
Based upon the information that you will be using MySQL, you will need to make some modifications to the above query (which works with Microsoft SQL). You need to replace the RANK function with your own RANK implementation. It isn't that hard. Complete instructions can be found here:
http://thinkdiff.net/mysql/how-to-get-rank-using-mysql-query/
That will show you how to implement a counter that can give you a rank number.
Depending on what DBMS you use, you may be able to use row_number in some form
In SQL Server 2008 you can use
create table #player
( Player char, Score int )
insert into #player (Player, Score) Values
('A',5),('B',4),('A',3),('B',2),('A',1),('B',1),('A',2),('B',3),('A',4),('B',5)
select * from #player
select Player, Score from
(
select *, ROW_NUMBER() over(partition by Player order by Score desc) as rowNo
from #player
) as tmp
where tmp.rowNo <= 3
drop table #player