I've got a database with 120k players. Each entry contains id, score (and more).
The goal is to get a highscore-list of not the top players, but instead of the N players above and and below a player, given his ID.
I currently try to solve this using two queries.
Query 1:
SELECT (
SELECT COUNT(*)
FROM players p2
WHERE p2.score > p1.score
) AS rank
FROM players p1
WHERE id = ID
returns the rank RANK of the player with an offset of -1. (for the best player it'll return 0)
Query 2:
SELECT id, score
FROM players
ORDER BY score DESC
LIMIT X OFFSET RANK;
returns a list with X=2*N+1 entries. I shift the $rank by -n to have the player that is doing the request in the middle (n players higher, current player, n players below).
So far, so good.
The actual issue now is, that for some scores there are more players with this score than X is big, which sometimes results in the player that should be in the middle of the list not even being contained in the X entries, but in some entries above or below.
To me it seems like a consistency problem, that query 1 returns a rank Y for player Z, but query 2 doesn't have player Z at it's Y'th position.
Can these queries be merged, or is there any other nice solution to this?
If the above stated is not clear, here's a minimalistic example:
n=1, requesting player called: C
database: A:123, B:123, C:123, D:123
Query 1 returns rank 3 for player C
Query 2 returns A:123, B:123, D:123 (being ranks 2-4)
C:123 should be in the middle, but the sorting of query 2 had C as rank 1.
The order of the elements with the same score in query 2 seems randomly
You can get the rank (position) in the highscore-list with something similar to the following query:
select * from (
select #rank:=#rank+1 rank, p.id
from players p
order by score desc
) t, (select #rank:= 0) t2
where id = :UID
After this query you can change the outer select to only get rank in the range of "rank" +- N
Related
I'm trying to build a people directory like LinkedIn has:
http://www.linkedin.com/directory/people-a
I don't want to fetch all the rows with name field starting with a and build the link list like LinkedIn. Is there any way in MySQL so I can only fetch rows in this sequence:
1st, 100th,101st,200th,201st,300th,301st,400th,401st, Last
That means I am trying to get two consecutive rows after a certain gap including the first and last item. The ids are not in nice uniformly increasing order, so I can't use this answser. Any help or hint is appreciated.
Say my query SELECT * FROMbusinesseswhere name like 'a%' order by name returns id like this:
1,3,5,6,8, 9,12,33,45,66,77,88,100,103,120,133,155,166,177,178,198
Above is if I want to get all the rows. But what I want is to get only the items after a certain distance. For example if I want to pick after every 5 items:
1,9,12, 88,100,166,177,198
So skip 4 items and take next two. Is that even possible in mysql?
you can order by whatever you want
create some function that gets a number and return 0,1 (all the numbers you specified will be 0 for example), then you need to pass the rank of the row to your function.
example
select first_name,rank
from (
SELECT first_name,
#curRank := #curRank + 1 AS rank
FROM person p, (SELECT #curRank := 0) r ) x
order by myfunc(x.rank);
myfunc being the function you wrote
if you want to only get those rows, not just sort by it and get them first you can do the following :
select first_name,rank
from (
SELECT first_name,
#curRank := #curRank + 1 AS rank
FROM person p, (SELECT #curRank := 0) r ) x
where rank in (1,100,101,102...)
You can select a range with the limit clause
SELECT ...... LIMIT 0,99
This will give the first 100 records
SELECT ...... LIMIT 100,199
will give the next & so on
Im having issues performing a select on a table, but using the results of another table to filter the possibilities.
I have a table of player results (player id, wins, losses, draws, points), and rounds for the tournament are in another table (round id, tournament id, player 1 id, player 2 id and table id).
I've been trying to get a select statement that checks the tournamentround table and prevents the player from playing previous opponents (like a swiss tournament), but I run into the fact the player it choses for the second may then be chosen for another player.
ie p1 has played p2 and p3, but not p4 or p5, when the select happens it assigns p1 to p4, but when it gets to p4 it assigns p4 to p2.
Any idea on how to prevent this?
Example select:
SELECT * FROM playerresults WHERE ((SELECT pid FROM playerresults) != (SELECT p1id FROM tournamentrounds))
Any suggestions would be greatly appreciated
If you have a given player, say #playerid, then you can get players that have not been played by doing something like:
select pr.*
from playerresults pr
where pr.pid not in (select t.pid2 from tournaments t where pid1 = #playerid) and
pr.pid not in (select t.pid1 from tournaments t where pid2 = #playerid)
If you want a random one and your data is not very large (less than a few thousand rows), just add:
order by rand()
limit 1
to the query.
Recently began working on a matchmaking system, where in this system there are 2 tables. One table is for matches and one table is for ranks.
The table for ranks starts off like this
table_ranks:
date
playeruid
rank
table_matches:
date
playeruid
playerpoints
I'm trying to now order the player by their points. However, in order to do this, I have made a query:
SELECT * FROM table_ranks ORDER by rank DESC
But now what I realize, is that I need to add the playerpoints on top of the rank. So basically, I need to add playerpoints to the player's current ranking. So, if I had this for an example row:
table_ranks:
2/20/15
Player1
56
table_matches:
2/27/15
Player1
5
I would need to build a query that takes the 56 of player1, looks for player1 in the matches and anywhere it sees it, it would need to add his 5 points making it a sum of 56. Once this is determined it would ORDER by this value in order to determine who is ranked with what. How do I begin my query? I understand that in order to join the tables, I need to start off like this:
"SELECT table_ranks., table_matches. from table_ranks, table_matches ORDER by RANK..."
Then to finish it,I would have to take the current value of the rank, then grab the specific player it's referring to and take all the matches and add up all the playerpoints to his rank then to determine how to order it by.
Try this:
SELECT r.playeruid, r.date AS rank_date, m.date AS macthes_date,
(r.rank + m.playerpoints) AS total_points
FROM table_ranks r INNER JOIN table_matches m ON r.playeruid = m.playeruid
ORDER BY total_points DESC
This query assumes that playeruid is unique in both tables.
Try the following query. I tested on a similartable structure and it should work
SELECT * , playeruid AS player_id, (
SELECT SUM( playerpoints )
FROM `table_matches`
WHERE playeruid = player_id
) AS points
FROM table_ranks
ORDER BY points DESC
I have a table called user_rankings where votes (voted) are stored for each user. I want to display the current ranking of users (this week) that depends on how much votes the user got.
example to clarify:
RANK-NR, USERNAME, VOTED,
1, name1, 18 times
2, name1, 16 times
(my ranking here), myname, 13 times
In this example my ranking should be 3. If I'd have 17 votes, I would be number 2. If there would be five users above me, I would be number 8. I guess you get the point.
Now I can display the ranking number easily with an incrementing $i in PHP. But I only want to show a list limited to ten users (a top ten list) and directly after that my current ranking, if I'm not already in that top ten list. So I'm just wondering how to get my exact ranking number using MySQL.
I'm assuming to have hundreds of users in this list with a different amount of votes.
This is my statement at the moment:
SELECT
`voted`
FROM `users_ranking`
WHERE
`uid`='".$_SESSION['uid']."'
AND
WEEKOFYEAR(`date`)=WEEKOFYEAR(NOW())
LIMIT 1
I can't give you the exact code, but i think the following can give you some idea
select 'RANK-NR', 'USERNAME', 'VOTED' from
(
select 'RANK-NR', 'USERNAME', 'VOTED', rank() over (order by 'voted' desc) as rank
from users_ranking
where
uid='".$_SESSION['uid']."'
AND
WEEKOFYEAR(date)=WEEKOFYEAR(NOW())
) as abc
where
rank<11
i think rank() over (order by<>) should work
I just found out myself that this solution works:
SELECT *
FROM
(
SELECT #ranking:= #ranking + 1 rank,
a.`uid`
FROM `users_ranking` a, (SELECT #ranking := 0) b
ORDER BY a.`votes` DESC
) s
WHERE `uid`='".$_SESSION['uid']."'
AND
WEEKOFYEAR(`date`)=WEEKOFYEAR(NOW())
LIMIT 1
OK, example to go with my comment. What you have will often work, but there is nothing to force MySQL to do the sort before it applies the ranking.
As such using an extra level of sub query would give you this (not tested). The inner sub query is getting all the user ids for the relevant week in the right order, while the next outer sub query applies the ranking to this ordered result set. The outer query just gets the single returned row you require.
SELECT c.rank, c.uid
FROM
(
SELECT #ranking:= #ranking + 1 rank, a.uid
FROM
(
SELECT uid, votes
FROM `users_ranking`
WHERE WEEKOFYEAR(`date`) = WEEKOFYEAR(NOW())
ORDER BY votes DESC
) a,
(SELECT #ranking := 0) b
) c
WHERE c.uid = '".$_SESSION['uid']."'
LIMIT 1
Another possibility avoiding the sub query and also avoiding the need for a variable is to do a join. This is (mis)using HAVING to slim down the result to the single row you are interested in. Down side of this solution is that if multiple users have the same score they will each get the same ranking.
SELECT b.uid, COUNT(a.uid)
FROM users_ranking a
LEFT OUTER JOIN users_ranking b
ON WEEKOFYEAR(a.`date`) = WEEKOFYEAR(b.`date`)
AND a.votes >= b.votes
GROUP BY b.uid
HAVING b.uid = '".$_SESSION['uid']."'
EDIT
To give the top 10 rankings:-
SELECT b.uid, COUNT(a.uid) AS rank
FROM users_ranking a
LEFT OUTER JOIN users_ranking b
ON WEEKOFYEAR(a.`date`) = WEEKOFYEAR(b.`date`)
AND a.votes >= b.votes
GROUP BY b.uid
ORDER BY rank
LIMIT 10
Although in this case it might be quicker to use a sub query. You could then put the LIMIT clause in the sub query with the ORDER BY, hence it would only need to use the variables to add a rank to 10 rows.
I am not sure how to combine that with the query for a single user, mainly as I am not sure how you want to merge the 2 results together.
I'm using a MySQL database to store scores for my game. A very simplified version of the table would be
(PlayerID - int)
(Name - string)
(Score - int)
I'd like to form a query that would return me a set of say 10 results where the player of interest is in the middle of the table.
Perhaps an example would make it more clear.
I have just achieved a high score, my name is Steve. When I look at the high score table I'd like to see the 5 scores below me and the 5 scores above me. Obviously if I have the top score I will see the 9 scores below me, and conversely, if I am at the bottom I will see the 9 scores above me. The score table could consist of thousands of scores.
As the database is specifically designed for querying sets of data I'd like to reduce the amount of post processing on the results.
Does anyone have an idea for a query to achieve this?
Thanks
Rich
if you have the user id and score (userId and userScore) you can do:
SELECT * FROM Scores
WHERE PlayerID = userId
OR PlayerID IN (SELECT PlayerID FROM Scores WHERE Score > userScore ORDER BY Score LIMIT 5)
OR PlayerID IN (SELECT PlayerID FROM Scores WHERE Score < userScore ORDER BY Score DESC LIMIT 5)
ORDER BY Score
EDIT: following your comments:
First of all, you need to know the rank of the current user. You can know it with such a query as SELECT COUNT(*) FROM Scores WHERE Score > userScore
It gives you a number n
Then you compute two numbers usersUp and usersDown such as usersDown + userUp = 10 and n - userUp>0 and n + usersDown < SELECT COUNT(*) FROM Scores
Then you can use a UNION instead of IN and Subqueries:
SELECT * FROM Scores WHERE PlayerID = userId
UNION
SELECT * FROM Scores WHERE Score > userScore ORDER BY Score LIMIT usersUp
UNION
(SELECT * FROM Scores WHERE Score < userScore ORDER BY Score LIMIT usersDown)
if you want to stay on the mysql server side you can gather all that in a stored procedure