Getting player rank from database - sql

I have a RuneScape private server, which stores the player scores in a database.
The highscores load the player's scores and put them into a table.
But now comes the harder part I can't fix:
I want to display the rank of the player. Like: 'Attack level: 44, ranked 12'. So it has to find the rank the user has.
How can I get this to work? I googled for 2 days now, I did not find anything.

I don't know if there's a way to achieve this using the same query.
You could make another query like:
pos = select count(*) from players where attack > 44 + 1
This query would return the number of players ranked above someone. The "plus one" part is to make the rank start at 1 (because the first one won't have anyone ranked above him).
For example, if the table is:
id attack
0 35
1 22
2 121
3 76
pos(3) = 1 (only player 2 is ranked above) + 1 = 2

You can create a view (probably) that shows every players score. Something along these lines might work.
create view player_scores as
select player_id, sum(score)
from scores
group by player_id
That will give you one row per player, with their total score. Having that view, the rank is simple.
select count(*)
from player_scores
where sum > (select sum from player_scores where player_id = 1)
That query will return the number of players having a higher score than player_id = 1.
Of course, if you know your player's score before you run the query, you can pass that score as a parameter. That will run a lot faster as long as the column is indexed.

Related

SQL return minimum by groupby and ratio of

A SQL question: I have a table game with columns user_id (unique per user), game_id (unique per game), game_start_timestamp_utc (the UTC timestamp for when the game starts), and game_status, which can either be ‘pass’, ‘in progress’ or ‘fail’.
The question is to write a query to return the game that has the lowest pass rate (pass users/enrolled users).
The table should be like this
user_id game_id game_start_timestamp_utc game_status
-----------------------------------------------------
1 111 10/22/2019 pass
2 111 10/21/2018 fail
...
I know how to do it in Python pandas, just need group by game_id to calculate pass rate, but have not much idea to do it in SQL. Thanks in advance.
Use conditional aggregation. avg() comes handy for this:
select game_id,
avg(case when game_status = 'pass' then 1.0 else 0 end) as pass_rate
from game
group by game_id
order by pass_rate
This gives you the pass rate of each game, as a value between 0 and 1, ordered by increasing rate - so the first row is the result you want.
You can keep that one row only with a row-limiting clause. The syntax varies across databases: limit 1, top (1), fetch first row, ...

Group by demands us to include all selected rows, when we need the results grouped by just one row

Following this programming exercise: SQL with Street Fighter, which statement is:
It's time to assess which of the world's greatest fighters are through
to the 6 coveted places in the semi-finals of the Street Fighter World
Fighting Championship. Every fight of the year has been recorded and
each fighter's wins and losses need to be added up.
Each row of the table fighters records, alongside the fighter's name,
whether they won (1) or lost (0), as well as the type of move that
ended the bout.
id
name
won
lost
move_id
winning_moves
id
move
However, due to new health and safety regulations, all ki blasts have
been outlawed as a potential fire hazard. Any bout that ended with
Hadoken, Shouoken or Kikoken should not be counted in the total wins
and losses.
So, your job:
Return name, won, and lost columns displaying the name, total number of wins and total number of losses. Group by the fighter's
name.
Do not count any wins or losses where the winning move was Hadoken, Shouoken or Kikoken.
Order from most-wins to least
Return the top 6. Don't worry about ties.
How could we group the fighters by their names?
We have tried:
select name, won, lost from fighters inner join winning_moves on fighters.id=winning_moves.id
group by name order by won desc limit 6;
However it displays:
There was an error with the SQL query:
PG::GroupingError: ERROR: column "fighters.won" must appear in the
GROUP BY clause or be used in an aggregate function LINE 3: select
name, won, lost from fighters inner join winning_move...
In addition we have also tried to include all selected rows:
select name, won, lost from fighters inner join winning_moves on fighters.id=winning_moves.id
group by name,won,lost order by won desc limit 6;
But the results differ from the expected.
Expected:
name won lost
Sakura 44 15
Cammy 44 17
Rose 42 19
Karin 42 13
Dhalsim 40 15
Ryu 39 16
Actual:
name won lost
Vega 2 1
Guile 2 1
Ryu 2 1
Rose 1 0
Vega 1 0
Zangief 1 0
Besides we have read:
https://www.w3schools.com/sql/sql_join.asp
MySql Inner Join with WHERE clause
How to limit rows in PostgreSQL SELECT
https://www.w3schools.com/sql/sql_groupby.asp
GROUP BY clause or be used in an aggregate function
PostgreSQL column must appear in the GROUP BY clause or be used in an aggregate function when using case statement
must appear in the GROUP BY clause or be used in an aggregate function
I guess you need to have sum() to aggregate the ids wins n loss. In addition to that you dont need join as you dont wanna show the move in the first query
select name, sum(won) as wins,
sum(lost)
from fighters
group by name order by sum(won)
desc limit 6;

Find correct place to insert new hi-score record

Given the table:
ArcadeScores
------------
ID
GameID
UserID
Score
Milliseconds
Rank
Where Rank is > 0 and calculated as the index of Score DESC then by Milliseconds ASC (best score is always top, in case of equal score it's ranked by whoever did it fastest).
Storing Rank is required as it allows me to perform fast queries such as How many top 3 scores does userID 5 have?.
Recalculating the Rank for a GameID when a new score is inserted by ordering all the records and looping each one updating the rank works OK, but looping through every record and performing an update query on every record slows down when you have thousands of records. For a popular game (especially a fast game where a single user might be posting a new score every 3 seconds or so), this is too costly.
Given a new score record, I need to work out which position it should be inserted into. If our new record is going to be rank 45 we can then increment every record above it by one which is a far cheaper operation:
UPDATE ArcadeScores SET ScoreRank = ScoreRank + 1 WHERE gameID = " + myGameID + " AND ScoreRank >= 45
The difficulty I'm having is working out the rank of a record of the record to insert. On Score or Milliseconds alone it's fairly easy, but I'm struggling to make it discover the correct Rank as a combination of both.
How many score records there are for a game is a known value.
Do you need a query or maybe you can use a function? Try this query - if i understood order in your table correctly, it will give a rank for the new row with values inserting_score and inserting_milliseconds:
SELECT COUNT(1) + 1 FROM ArcadeScores
WHERE Score > inserting_score OR (Score = inserting_score AND Milliseconds < inserting_millisecondes)
Oh, forgot about GameID :)
SELECT COUNT(1) + 1 FROM ArcadeScores
WHERE GameID = inserting_gameid AND (Score > inserting_score
OR (Score = inserting_score AND Milliseconds < inserting_millisecondes))

Retrieve names by ratio of their occurrence

I'm somewhat new to SQL queries, and I'm struggling with this particular problem.
Let's say I have query that returns the following 3 records (kept to one column for simplicity):
Tom
Jack
Tom
And I want to have those results grouped by the name and also include the fraction (ratio) of the occurrence of that name out of the total records returned.
So, the desired result would be (as two columns):
Tom | 2/3
Jack | 1/3
How would I go about it? Determining the numerator is pretty easy (I can just use COUNT() and GROUP BY name), but I'm having trouble translating that into a ratio out of the total rows returned.
SELECT name, COUNT(name)/(SELECT COUNT(1) FROM names) FROM names GROUP BY name;
Since the denominator is fixed, the "ratio" is directly proportional to the numerator. Unless you really need to show the denominator, it'll be a lot easier to just use something like:
select name, count(*) from your_table_name
group by name
order by count(*) desc
and you'll get the right data in the right order, but the number that's shown will be the count instead of the ratio.
If you really want that denominator, you'd do a count(*) on a non-grouped version of the same select -- but depending on how long the select takes, that could be pretty slow.

MySQL: Getting highest score for a user

I have the following table (highscores),
id gameid userid name score date
1 38 2345 A 100 2009-07-23 16:45:01
2 39 2345 A 500 2009-07-20 16:45:01
3 31 2345 A 100 2009-07-20 16:45:01
4 38 2345 A 200 2009-10-20 16:45:01
5 38 2345 A 50 2009-07-20 16:45:01
6 32 2345 A 120 2009-07-20 16:45:01
7 32 2345 A 100 2009-07-20 16:45:01
Now in the above structure, a user can play a game multiple times but I want to display the "Games Played" by a specific user. So in games played section I can't display multiple games. So the concept should be like if a user played a game 3 times then the game with highest score should be displayed out of all.
I want result data like:
id gameid userid name score date
2 39 2345 A 500 2009-07-20 16:45:01
3 31 2345 A 100 2009-07-20 16:45:01
4 38 2345 A 200 2009-10-20 16:45:01
6 32 2345 A 120 2009-07-20 16:45:01
I tried following query but its not giving me the correct result:
SELECT id,
gameid,
userid,
date,
MAX(score) AS score
FROM highscores
WHERE userid='2345'
GROUP BY gameid
Please tell me what will be the query for this?
Thanks
Requirement is a bit vague/confusing but would something like this satisfy the need ?
(purposely added various aggregates that may be of interest).
SELECT gameid,
MIN(date) AS FirstTime,
MAX(date) AS LastTime,
MAX(score) AS TOPscore.
COUNT(*) AS NbOfTimesPlayed
FROM highscores
WHERE userid='2345'
GROUP BY gameid
-- ORDER BY COUNT(*) DESC -- for ex. to have games played most at top
Edit: New question about adding the id column to the the SELECT list
The short answer is: "No, id cannot be added, not within this particular construct". (Read further to see why) However, if the intent is to have the id of the game with the highest score, the query can be modified, using a sub-query, to achieve that.
As explained by Alex M on this page, all the column names referenced in the SELECT list and which are not used in the context of an aggregate function (MAX, MIN, AVG, COUNT and the like), MUST be included in the ORDER BY clause. The reason for this rule of the SQL language is simply that in gathering the info for the results list, SQL may encounter multiple values for such an column (listed in SELECT but not GROUP BY) and would then not know how to deal with it; rather than doing anything -possibly useful but possibly silly as well- with these extra rows/values, SQL standard dictates a error message, so that the user can modify the query and express explicitly his/her goals.
In our specific case, we could add the id in the SELECT and also add it in the GROUP BY list, but in doing so the grouping upon which the aggregation takes place would be different: the results list would include as many rows as we have id + gameid combinations the aggregate values for each of this row would be based on only the records from the table where the id and the gameid have the corresponding values (assuming id is the PK in table, we'd get a single row per aggregation, making the MAX() and such quite meaningless).
The way to include the id (and possibly other columns) corresponding to the game with the top score, is with a sub-query. The idea is that the subquery selects the game with TOP score (within a given group by), and the main query's SELECTs any column of this rows, even when the fieds wasn't (couldn't be) in the sub-query's group-by construct. BTW, do give credit on this page to rexem for showing this type of query first.
SELECT H.id,
H.gameid,
H.userid,
H.name,
H.score,
H.date
FROM highscores H
JOIN (
SELECT M.gameid, hs.userid, MAX(hs.score) MaxScoreByGameUser
FROM highscores H2
GROUP BY H2.gameid, H2.userid
) AS M
ON M.gameid = H.gameid
AND M.userid = H.userid
AND M.MaxScoreByGameUser = H.score
WHERE H.userid='2345'
A few important remarks about the query above
Duplicates: if there the user played several games that reached the same hi-score, the query will produce that many rows.
GROUP BY of the sub-query may need to change for different uses of the query. If rather than searching for the game's hi-score on a per user basis, we wanted the absolute hi-score, we would need to exclude userid from the GROUP BY (that's why I named the alias of the MAX with a long, explicit name)
The userid = '2345' may be added in the [now absent] WHERE clause of the sub-query, for efficiency purposes (unless MySQL's optimizer is very smart, currently all hi-scores for all game+user combinations get calculated, whereby we only need these for user '2345'); down side duplication; solution; variables.
There are several ways to deal with the issues mentioned above, but these seem to be out of scope for a [now rather lenghty] explanation about the GROUP BY constructs.
Every field you have in your SELECT (when a GROUP BY clause is present) must be either one of the fields in the GROUP BY clause, or else a group function such as MAX, SUM, AVG, etc. In your code, userid is technically violating that but in a pretty harmless fashion (you could make your code technically SQL standard compliant with a GROUP BY gameid, userid); fields id and date are in more serious violation - there will be many ids and dates within one GROUP BY set, and you're not telling how to make a single value out of that set (MySQL picks a more-or-less random ones, stricter SQL engines might more helpfully give you an error).
I know you want the id and date corresponding to the maximum score for a given grouping, but that's not explicit in your code. You'll need a subselect or a self-join to make it explicit!
Use:
SELECT t.id,
t.gameid,
t.userid,
t.name,
t.score,
t.date
FROM HIGHSCORES t
JOIN (SELECT hs.gameid,
hs.userid,
MAX(hs.score) 'max_score'
FROM HIGHSCORES hs
GROUP BY hs.gameid, hs.userid) mhs ON mhs.gameid = t.gameid
AND mhs.userid = t.userid
AND mhs.max_score = t.score
WHERE t.userid = '2345'