Calculating rank not working - Mysql - mysql

I have a DB Table user_points which contains user's points and I am trying to calculate ranking based on points. It is working fine for all users except users having 1 point.
If user have 1 point it is showing it's rank as 0 but it should display it's rank as last or in last numbers like: 12083 etc.
Higher points are, ranking should be higher as well. For example:
1000 points = rank 1
1 point = rank 1223
Following is the query.
SELECT id, mobileNo, points,
FIND_IN_SET( points, (SELECT GROUP_CONCAT( points ORDER BY points DESC )
FROM users_points )) AS rank
FROM users_points
WHERE mobileNo = '03214701777'
What should I change to fix it?

SELECT a.id, a.mobileNo, a.points,
IFNULL((SELECT COUNT(*) AS rank
FROM users_points b
WHERE b.points<a.points), 0)+1 as rank
FROM user_points a
WHERE a.mobileNo = '03214701777'
Seems to be what you are looking for. While it is still very innefficient it is better than your approach using FIND_IN_SET(). If you really want to use FIND_IN_SET() then you need to pad the scores to a consistent width and divide by the width+1 to get the rank.

Related

MySQL Return latest and longest streak of rows with certain conditions

Please have a look at this fiddle.
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/7
I'm trying to accomplish 2 things:
How do I get the length and date of the LATEST STREAK OF CORRECT GUESSES (meaning Result = Guess) without any skipped dates? In this case, it would be 4, starting from 2021-01-05 to 2021-01-08. (Although 2021-01-03 is correct, because there was no guess on 2021-01-04, it should not be included).
How do I get the length and date of the LONGEST STREAK OF CORRECT GUESSES OF ALL TIME? Again meaning Result = Guess, but can be anywhere in the table. Let's say it's 10 from 3 months ago.
To further complicate things, guesses can be made by multiple users AND there will be multiple results (for different game categories for example) on the same day. So the table above is for one user and one game category. I think I can handle this if I can get some guidance on the goals up above.
This is beyond my understanding. Any and all help would be appreciated.
EDIT: I've changed the table to show that the date is not always sequential. Also, I was informed that I should be using MySQL 8.0 for this task as using variables is not good practice for this problem.
Edit: Using the window functions, starting to get somewhere:
Please check the fiddle. It's pretty close to what I'm trying to get to, but the '4' in the total column should be a 1. In other words, the "sum" should restart. Not sure how to achieve this, because it's clear that the window function will group based on the conditions, breaking the order and thus the streak.
Updated: I've updated the fiddle per #The Impaler's request. The table here is more representative to what I'm actually working with (still not exact, but much closer).
Since this new fiddle is more representative, I'll also explain my final goal. I'd also like to get the streak for each game_type. The way I've been comparing game_type result on a given day to "community" (basically all the users) guess is by summing all the 0's and 1's for each game_type on that date from all the users and then using whichever greater as the 'guess'. This way, I can get how the "community" is doing as a whole. This works for individual dates, but to do a streak, I'm not sure.
Update 2
So this is as far as I've got:
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/11
I tried to do a nested window function but that's not allowed. I have the proper groupings and column for when guess = result. Now I need help figuring out the streak within the groups.
This is a typical "Gaps & Islands" problem. Once you assemble the islands the query becomes easy.
For example, for a single user, as stated in the fiddle you can get the LONGEST STREAK by doing:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by streak_length desc
limit 1;
Result:
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
To get the LATEST STREAK you just need to change the ORDER BY clause at the end as shown below:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by ending_day desc
limit 1;
Result (same result as before):
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
See running example at DB Fiddle.
Note: You can remove the LIMIT clause at the end to see all the islands, not just the selected one.
For multi-users it's just a matter of modifying the windows (adding partitioning) and the rest of the query remains the same. If you provide a fiddle for multi-users I can add the solution as well.
So, it took a while, but thanks to #The Impaler providing me the basis and the link below, I was able to solve the problem.
https://www.red-gate.com/simple-talk/sql/t-sql-programming/efficient-solutions-to-gaps-and-islands-challenges/
Here is the full solution:
with GAME_LOG as (
select
*,
guess = result as correct,
lag(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as previous_game_result,
lead(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as next_game_result,
row_number() over(partition by user_id, game_type order by dayt DESC) as ilocation
from mytable
),
CTE_ISLAND_START as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_start_time,
ilocation as island_start_location
from GAME_LOG
where correct = 1 AND
(previous_game_result <> 1 OR previous_game_result is null)
),
CTE_ISLAND_END as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_end_time,
ilocation as island_end_location
from GAME_LOG
where correct = 1 AND
(next_game_result <> 1 OR next_game_result is null)
)
select
CTE_ISLAND_START.user_id,
CTE_ISLAND_START.game_type,
CTE_ISLAND_START.island_start_time as streak_end,
CTE_ISLAND_END.island_end_time as streak_start,
cast(CTE_ISLAND_END.island_end_location as signed) -
cast(CTE_ISLAND_START.island_start_location as signed) + 1 as streak
from CTE_ISLAND_START
inner join CTE_ISLAND_END
on CTE_ISLAND_START.inumber = CTE_ISLAND_END.inumber AND
CTE_ISLAND_START.user_id = CTE_ISLAND_END.user_id AND
CTE_ISLAND_START.game_type = CTE_ISLAND_END.game_type
This will give all the streaks for each user_id, each game_type, as well as the start and end dates of the streak.
You can simply add a WHERE clause to filter by game_type and user_id.
Here's the fiddle with slightly updated dataset.
Fiddle

MYSQL query and pulling second column of data based on first column

Hopefully I can explain this well. I'm trying to pull in my database the max points, assists, and rebounds from one column and then based on that number, grab the game number (gid) where each of those numbers came from. I started it with something simple, thinking I could get it, but the gid it grabs is just the first id for that particular season, not for the right game in any case.
SELECT gid, sid, max(points), max(assists), max(rebounds)
FROM game_stats_lakers
WHERE playerid = 2
GROUP By gid
I want to get the gid (which is the game id) for the max points, assists, and rebounds which most likely will be different for each. I can't seem to figure out how to pull any gid correctly.
Thanks for any help!
Hmmm . . . Is this what you want?
(select gsl.*
from game_stats_lakers gsl
where playerid = 2
order by gsl.points desc
limit 1
) union all
(select gsl.*
from game_stats_lakers gsl
where playerid = 2
order by gsl.assists desc
limit 1
) union all
(select gsl.*
from game_stats_lakers gsl
where playerid = 2
order by gsl.rebounds desc
limit 1
) ;

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');

to get top 5 rankings from database

In my table i have team and points column and I want to get top 5 teams .Teams with same points should be grouped and consider as one of the ranks so if 5 teams are having same points then all should come as one of the rank and next suceeding records according to team points
TRY
SELECT DISTINCT(point), team
FROM tableTeam
ORDER BY points DESC LIMIT 5
SELECT team,
points,
(SELECT COUNT(*)
FROM teams t2
WHERE t2.points > t1.points) + 1 rank
FROM teams t1
ORDER BY points DESC
LIMIT 5
There's no window functions in MySQL, so you'll want to extract the rank in your scripts.
Also, if I'm making sense of your ranking criteria, you're actually interested in getting the top 5 teams plus any additional teams that might have the same number of points as that in the 5th rank.
If so, your limit should be applied to a subquery on the point criteria:
select name, points
from teams
where points >= (
select points
from teams
order by points desc
limit 1 offset 4
)
order by points desc, name
If not, a simple order by/limit will do:
select name, points
from teams
order by points desc, name
limit 5

Getting difference between counts of two subqueries

I'm trying to determine the score of an entry by finding the difference between the number of upvotes and downvotes it has received in MYSQL by running SELECT (SELECT COUNT(vote_id) AS vote_up FROMvotesWHERE vote='UP'),(SELECT COUNT(vote_id) AS vote_down FROMvotesWHERE vote='DOWN'),(vote_up - vote_down AS vote_score). When I try to run this though, it tells me that I do not have proper syntax. What am I doing wrong?
Also, is there a better way to write this?
And finally, what is the ideal way to find the item with the highest and lowest number of votes? Would I just ORDER BY [above query]?
You can do it with
SELECT some_id
, SUM(
CASE
WHEN vote = 'UP'
THEN 1
WHEN vote = 'DOWN'
THEN -1
ELSE 0
END
) as vote_score
FROM votes
GROUP BY some_id
Note that the better approach is to have +1 or -1 stored in vote, then you can just do:
SELECT some_id, SUM(vote) as vote_score
FROM votes
GROUP BY some_id
BTW if my formatting looks odd to you, I explained it in http://bentilly.blogspot.com/2011/02/sql-formatting-style.html.
You can do it by pulling that last clause into a (SELECT ...) block as well:
SELECT
(SELECT COUNT(vote_id) FROM votes WHERE vote='UP') AS vote_up,
(SELECT COUNT(vote_id) FROM votes WHERE vote='DOWN') AS vote_down,
(SELECT vote_up - vote_down) AS vote_score
ORDER BY vote_whatever;
Note btilly's answer about having +/- 1 be the upvote / downvote representation. It makes a lot more sense in this context, and allows for smaller tables, faster comparisons, and use of the SUM() function when necessary:
SELECT SUM(vote) from votes;
Also note: You'll only get vote_up and vote_down counts using the multiple (SELECT ...) method - SUM(CASE) will only give you the total.
Following up on btilly's answer, If you need to know the lowest and highest but do not need to know what ID has the highest/lowest:
SELECT MIN(score), MAX(score)
FROM (
SELECT SUM(IF(vote = 'DOWN', -1, vote = 'UP')) AS score
FROM votes
GROUP BY ID
)
If you do need to know the ID, use the inner query (add the ID to the select) with a ORDER BY score LIMIT 1 to get the lowest and ORDER BY score DESC LIMIT 1 to get the highest.
note in the case of ties, this will choose only 1 of them.