I got a table like this:
users:
| username | statistics |
----------- ---------------------------------------
0 | peter200 | { "gamesWon": 4, "gamesPlayed" : 4} |
1 | eminem33 | { "gamesWon": 7, "gamesPlayed" : 20} |
Note: (statistics = "JSON")
And I'd like to create a rank-list.
So the user with the highest number in statistics. gamesWon gets rank numero uno 1 and so on.
What I've got so far is something like this (what is exactly working like I've hoped):
SELECT username, statistics, #rank := #rank + 1 AS rank
FROM users, (SELECT #rank := 0) r
WHERE JSON_EXTRACT(statistics, '$.gamesWon')
ORDER BY JSON_EXTRACT(statistics, '$.gamesWon') DESC
So to my question: Now I'd like to update the query from above to just get the information in an specific rank-index (lets say from rank 2 to rank 10).
Adding AND rank > 2 AND rank < 10 to the WHERE clause does not seems to be a working solution. So any help would be really appreciated.
You need to wrap it with subquery:
SELECT *
FROM (SELECT username, statistics, #rank := #rank + 1 AS rank
FROM users, (SELECT #rank := 0) r
WHERE JSON_EXTRACT(statistics, '$.gamesWon')
ORDER BY JSON_EXTRACT(statistics, '$.gamesWon') DESC
) s
WHERE rank > 2 AND rank < 10
You don't actually need the subquery. You can use limit with offset:
SELECT username, statistics, #rank := #rank + 1 AS rank
FROM users CROSS JOIN (SELECT #rank := 0) r
WHERE JSON_EXTRACT(statistics, '$.gamesWon')
ORDER BY JSON_EXTRACT(statistics, '$.gamesWon') DESC
LIMIT 1, 9
Related
I have a query that calculates dense ranks based on the value of a column :
SELECT id,
score1,
FIND_IN_SET
(
score1,
(
SELECT GROUP_CONCAT(score1 ORDER BY score1 DESC) FROM scores
)
) as rank
FROM score_news;
This is what the query results look like:
+----+--------+------+
| id | score1 | rank |
+----+--------+------+
| 1 | 15 | 1 |
| 2 | 15 | 1 |
| 3 | 14 | 3 |
| 4 | 13 | 4 |
+----+--------+------+
The query takes Nx longer time when number of scores increases by N times. Is there any way I can optimize this ? My table size in the order of 106
NOTE: I have already tried a technique using mysql user variables but I get inconsistent results when I run it on a large set. On investigation I found this in the MySQL docs:
The order of evaluation for user variables is undefined and may change
based on the elements contained within a given query. In SELECT #a, #a
:= #a+1 ..., you might think that MySQL will evaluate #a first and
then do an assignment second, but changing the query (for example, by
adding a GROUP BY, HAVING, or ORDER BY clause) may change the order of
evaluation...The general rule is never to assign a value to a user
variable in one part of a statement and use the same variable in some
other part of the same statement. You might get the results you
expect, but this is not guaranteed.
My attempt with user variables :
SELECT
a.id,
#prev := #curr as prev,
#curr := a.score1 as curr,
#rank := IF(#rank = 0, #rank + 1, IF(#prev > #curr, #rank+#ties, #rank)) AS rank,
#ties := IF(#prev = #curr, #ties+1, 1) AS ties
FROM
scores a,
(
SELECT
#curr := null,
#prev := null,
#rank := 0,
#ties := 1,
#total := count(*)
FROM scores
WHERE score1 is not null
) b
WHERE
score1 is not null
ORDER BY
score1 DESC
)
The solution with variables could work, but you need to first order the result set, and only then work with the variable assignments:
SELECT a.id,
#rank := IF(#curr = a.score1, #rank, #rank + #ties) AS rank,
#ties := IF(#curr = a.score1, #ties + 1, 1) AS ties,
#curr := a.score1 AS curr
FROM (SELECT * FROM scores WHERE score1 is NOT NULL ORDER BY score1 DESC) a,
(SELECT #curr := null, #rank := 0, #ties := 1) b
NB: I placed the curr column last in the select clause to save one variable.
You can also use following query to get your dense rank without using user defined variables
SELECT
a.*,
(SELECT
COUNT(DISTINCT score1)
FROM
scores b
WHERE a.`score1` < b.score1) + 1 rank
FROM
scores a
ORDER BY score1 DESC
Demo
Demo using your data set
An index on score1 column might help you
I have a below table and wants to select only last 2 entries of all users.
Source table:
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 10 2016-5-12
2 10 2016-5-12
1 11 2016-6-12
2 12 2016-8-12
3 12 2016-8-12
2 13 2016-8-12
1 14 2016-9-12
3 14 2016-9-12
3 11 2016-6-12
Expected output is like, (should list only recent 2 quizid entries for all users)
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 14 2016-9-12
1 11 2016-6-12
2 13 2016-8-12
2 12 2016-8-12
3 14 2016-9-12
3 12 2016-8-12
Any idea's to produce this output.
Using MySQL user defined variables you can accomplish this:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2
Working Demo
Note: If you want at most x number of entries for each user then change the condition in where clause like below:
WHERE t.row_number <= x
Explanation:
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC;
This query sorts all the data in ascending order of userId and descending order of quizendtime(AID).
Now take a walk on this (multi) sorted data.
Every time you see a new userId assign a row_number (1). If you see the same user again then just increase the row_number.
Finally filtering only those records which are having row_number <= 2 ensures the at most two latest entries for each user.
EDIT: As Gordon pointed out that the evaluation of expressions using user defined variables in mysql is not guaranteed to follow the same order always so based on that the above query is slightly modified:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF (
#sameUser = UserId,
#a := #a + 1,
IF(#sameUser := UserId, #a := 1, #a:= 1)
)AS row_number
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2;
WORKING DEMO V2
User-defined variables are the key to the solution. But, it is very important to have all the variable assignments in a single expression. MySQL does not guarantee the order of evaluation of expressions in a select -- and, in fact, sometimes processes them in different orders.
select t.*
from (select t.*,
(#rn := if(#u = UserId, #rn + 1,
if(#u := UserId, 1, 1)
)
) as rn
from t cross join
(select #u := -1, #rn := 0) params
order by UserId, quizendtime desc
) t
where rn <= 2;
It's possible to create a query that return the x/y number of records?
Eg.
I have table like this
ID | id_user | id_event
23 | 3 | 1
24 | 3 | 1
25 | 3 | 1
26 | 4 | 2
27 | 4 | 2
I will return something that looks like this:
Event
id_user 3 -> **1/3**
id_user 3 -> **2/3**
id_user 3 -> **3/3**
id_user 4 -> **1/2**
id_user 4 -> **2/2**
Any suggestion is appreciated!
Try this
SET #id_event := 0;
SELECT CONCAT('id_user ', id_user ,'->','**', (#id_event := #id_event + 1) ,'/', id_user ,** ) from table
This is probably a duplicate to this question.
SELECT CONCAT('id_user ',id_user,' -> **',rank,'/',group_total,'**') FROM (
SELECT id,
group_total,
CASE id_user
WHEN #id_user THEN
CASE id_event
WHEN #id_event THEN #rowno := #rowno + 1
ELSE #rowno := 1
END
ELSE #rowno :=1
END AS rank,
#id_user := id_user AS id_user,
#id_event := id_event AS id_event
FROM event_table
JOIN (SELECT id_user, id_event, COUNT(*) group_total FROM event_table GROUP BY id_user, id_event) t USING (id_user, id_event)
JOIN (SELECT #rowno := 0, #id_user := 0, #id_event := 0) r
ORDER BY id_user, id_event
) c;
Assuming you want output like this:
id_user < id_user > ** serial number of event related to this user / total events related to this user **
You can accomplish such result by the following query:
SELECT
CONCAT('id_user ',UE.id_user,' -> **',IF(#userID = UE.id_user, #eventNumber := #eventNumber + 1, #eventNumber := 1),'/',t.totalEvents,'**') AS output,
#userID := UE.id_user
FROM (SELECT #userID := -1, #eventNumber := 1) var,user_events UE
INNER JOIN
(
SELECT
id_user,
COUNT(id_event) totalEvents
FROM user_events
GROUP BY id_user
) AS t
ON UE.id_user = t.id_user
ORDER BY UE.id_user;
SQL FIDDLE DEMO
More:
SQL FIDDLE DEMO 2
This particular fiddle returns only the desired output column whereas the first fiddle contains one extra column
I played a little bit and that would be my solution:
SELECT id, id_user, id_event, if(#n = a.id_event, #c:=#c+1, if(#n:=a.id_event, #c:=1, #c:=1)) as count, (SELECT count(*) from TABLE b WHERE a.id_user = b.id_user) as total, from TABLE a join (SELECT #n:= "", #c:=1) c
It just have two if conditions for counting a #c up if #n and id_user matches if not #n become id_user and #c is 1 again. The join is for initialize the var in the same query.
Thx to that question, i found the answer to a questions that i asked 4 days ago.
My Table gainfinal consists of three columns-countrycode, year and values. I want to select ten rows with top ten values. First, I created a rank according to values with the following Query.
SELECT countrycode, `values`,
#curRank := #curRank + 1 AS rank
FROM gainfinal CROSS JOIN
(SELECT #curRank := 0) vars
WHERE year = 2000
ORDER By `values` DESC ;
Now, I need to select the top ten rows with the highest rank. How can I do it ?
SELECT countrycode, `values`,
#curRank := #curRank + 1 AS rank
FROM gainfinal CROSS JOIN
(SELECT #curRank := 0) vars
WHERE year = 2000
ORDER By `values` DESC
LIMIT 10;
Use
LIMIT
in SQL so example:
SELECT countrycode, `values`,
#curRank := #curRank + 1 AS rank
FROM gainfinal CROSS JOIN
(SELECT #curRank := 0) vars
WHERE year = 2000
ORDER By `values` DESC
LIMIT 10
you can get 10 rows starting from row 20 using:Limit
LIMIT 10 OFFSET 20 --Equivalent to LIMIT 20, 10
You can also use your rank variable in where statement
...WHERE year = 2000 and #curRank <11;
I'm trying to modify the following query to find the rank of a specific videoid and I'm not having much luck can anyone suggest a solution?
SELECT videoid wins/loses as win_loss,
#curRank := #curRank + 1 AS rank
FROM cb_video,
(SELECT #curRank := 0) r
ORDER BY wins/loses DESC
I tried doing a subquery like this but it fails:
SELECT rank
FROM (SELECT videoid wins/loses as win_loss,
#curRank := #curRank + 1 AS rank
FROM cb_video,
(SELECT #curRank := 0) r
ORDER BY wins/loses DESC)
WHERE videoid = 116
Also adding the videoid to the WHERE clause without a subquery just always shows the rank being the #1 position as it only returns one row:
SELECT videoid wins/loses as win_loss,
#curRank := #curRank + 1 AS rank
FROM cb_video,
(SELECT #curRank := 0) r
WHERE videoid = 116
ORDER BY wins/loses DESC
Any ideas how to limit the result to a specific ID but still retain the rank? FYI I keep two columns (wins and loses) if that helps.
SELECT a.videoid,
(SELECT COUNT(*) FROM cb_video b
WHERE a.videoid !=b.videoid
AND (b.wins/b.loses) > (a.wins/a.loses))+1 AS rank
FROM cb_video a
WHERE a.videoid = 116
Try something like this:
SELECT videoid, rank FROM (SELECT videoid, wins/loses as win_loss, #curRank := #curRank + 1 AS rank FROM cb_video, (SELECT #curRank := 0) r ORDER BY wins/loses DESC) s WHERE videoid = 116
I've tested this on simple subset created of similar table as you've described...
It returns the ONE video and its actual final Rank of the entire set...
select *
from ( SELECT
videoid,
wins,
losses,
wins/losses,
#curRank := #curRank +1 Rank
FROM
cb_video,
( select #curRank := 0 ) r
order by
wins/losses desc ) PreQuery
where
PreQuery.VideoID = 116