MySQL - Getting a position from a "leaderboard" - mysql

I'm trying to construct a query where I would get a leaderboard-like result, then get the position on the leaderboard of a specific player, by their ID, and only if the player isHere = 'true' (stored in another table).
Table here:
userID | isHere |
--------------------
2 | true
--------------------
1 | true
--------------------
3 | false
--------------------
4 | true
Table userdata:
id | data |
------------------------------------
2 | {... "points": 5 ...}
------------------------------------
1 | {... "points": 10 ...}
------------------------------------
3 | {... "points": 2 ...}
------------------------------------
4 | {... "points": 28 ...}
Query:
SET
#row_number = 0;
SELECT
*
FROM
(
SELECT
(#row_number := #row_number +1) AS num,
userdata.id,
userdata.data
FROM
userdata
INNER JOIN
here ON userdata.id = here.userID
WHERE
here.isHere = 'true'
ORDER BY
JSON_EXTRACT(userdata.data,
'$.points') DESC
) AS t
WHERE
t.id = 1
This returns that num is 1... because for some reason it's sorting by userID/id. I double checked by setting WHERE t.id = as 2, and instead of returning 3, it returns 2... Does anyone know what's wrong with my query?
Note: I did try having the column data be just a BIGINT with the point value, but the same issue happened. So that eliminates JSON_EXTRACT() having any issues (I think?)
Note 2: Running the inner query on its own still orders by the ID with the num column, but displays in PHPMyAdmin in the correct order (Screenshot: https://gyazo.com/73177e79f4fedd4ec7e09ea0e70a9d2b)

So here's the query that works:
SET
#row_number = 0;
SELECT
*
FROM
(
SELECT
(#row_number := #row_number +1) AS num,
userdata.id,
userdata.data
FROM
userdata
INNER JOIN
(
SELECT
userdata.id,
userdata.data
FROM
userdata
INNER JOIN
here ON userdata.id = here.userID
WHERE
here.isHere = 'true'
ORDER BY
JSON_EXTRACT(userdata.data,
'$.points') DESC
) AS t ON userdata.id = t.id
) AS t2
WHERE
id = ?
The only reason all of this is needed, is due to the order in which MySQL executes parts of queries. Just like parenthesis are used in math in order of operations, I used a SELECT statement here.

Related

Does #row_number from MySQL 5 behave different in MySQL 8?

Simple table userpoints:
userid | points
1 | 456
2 | 3
3 | 1778
... | ...
I used this function for years in MySQL 5 to receive the userrank:
SELECT userid, userrank FROM
(SELECT #row_number:=#row_number+1 AS userrank, userid
FROM `userpoints`, (SELECT #row_number := 0) r
ORDER BY points DESC) t
WHERE userid = 123
And it returned the userrank for userid 123, e.g. 3456.
With MySQL 8 I only get 1 as value for userrank with each userid I try.
What is the problem and how to fix this?
I tried the inner SELECT alone, and this gives me the list of all userids with the correct userranks.
In MySQL 8, setting user variables as side-effects in expressions is deprecated. You should use window functions instead.
SELECT t.userid, t.userrank
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY points DESC) AS userrank, userid
FROM `userpoints`
) t
WHERE t.userid = 123;

Sequelize.js - Finding the rank of a row based on a column value

The problem
With a single query, I want to be able to find a row in my database table, and generate a virtual column that denotes the "rank" of that column, based on the position it comes in when ordering of another column.
Let's say I have this table in a mySQL DB:
id | score
1 | 400
2 | 700
3 | 200
4 | 800
Now I want to look up the row with the id of 3, and figure out what rank this row is in terms of score.
Obviously, looking at the table, if rank is assigned from highest score to lowest, row 3 would get a rank of 4, because it has the lowest score out of the 4 rows in the table. I do not want to findAll and sort in this case, because my real table is very large.
What I've tried
Model.findOne({
attributes: [
'id',
'score',
sequelize.literal('RANK() OVER (ORDER BY score DESC) rank')
]
});
This gives me this error:
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(ORDER BY score DESC) rank FROM `Model` AS `Model` LIMIT 1' at line 1",
I'd really appreciate any solutions / suggestions!
Edit: versions - mySQL 5.6, sequelize 4.35.2
did you mean something like this:
SELECT * FROM (
SELECT id, score, rank from
(
SELECT id, score, #tmprank := #tmprank + 1 AS rank
FROM scores
CROSS JOIN (SELECT #tmprank := 0) init
ORDER BY score DESC
) rt
ORDER BY rank
) AS r
WHERE r.id = 3;
sample
mysql> select * from scores;
+----+-------+
| id | score |
+----+-------+
| 1 | 400 |
| 2 | 700 |
| 3 | 200 |
| 4 | 800 |
+----+-------+
4 rows in set (0.00 sec)
mysql> SELECT * FROM (
-> SELECT id, score, rank from
-> (
-> SELECT id, score, #tmprank := #tmprank + 1 AS rank
-> FROM scores
-> CROSS JOIN (SELECT #tmprank := 0) init
-> ORDER BY score DESC
-> ) rt
-> ORDER BY rank
-> ) AS r
-> WHERE r.id = 3
-> ;
+----+-------+------+
| id | score | rank |
+----+-------+------+
| 3 | 200 | 4 |
+----+-------+------+
1 row in set (0.00 sec)
mysql>
I solved it by counting objects above "object" I'm looking for a rank.
const rank = await Game.count({
where: {
score: {
[Op.gt]: game.score // Op.gt it is syntax for grather then
},
}
})
rank++; // you have to add 1 to have the correct number
It's not the most efficient way, but it works.
Here are another Sequelizer's operators:
https://sequelizedocs.fullstackacademy.com/search-operators/
Have fun
I found a solution for it your approach is fine just syntax error
please find correct syntax bellow
Model.findOne({
attributes: [
'id',
'score',
[sequelize.literal('RANK() OVER (ORDER BY "score" DESC)'), 'rank']
]});

Can I rewrite this query without union clause

I have a sliders table. It is something that looks like this:
+----+-----------+-----------+
| id | video_url | image_url |
+----+-----------+-----------+
| 1 | null | imgurl1 |
+----+-----------+-----------+
| 2 | null | imgurl2 |
+----+-----------+-----------+
| 3 | null | imgurl3 |
+----+-----------+-----------+
| 4 | vidurl1 | null |
+----+-----------+-----------+
I can achieve what I want using this query:
(SELECT * FROM sliders WHERE image_url IS NOT NULL LIMIT 1)
UNION
(SELECT * FROM sliders WHERE video_url IS NOT NULL LIMIT 1)
UNION
(SELECT * FROM sliders)
Basically, the order I want is:
First Image
First Video
...
Everything else
So based on the example, the result should be (based on the id) is [1,4,2,3].
Is this possible to recreate without using UNION clause?
By the way, I am using Ruby on Rails on this project and currently using find_by_sql to execute the query. If you can help me use ActiveRecord instead, that would be great.
As of now, I can't see a way to union tables when using ActiveRecord.
Your query is no solution for the problem given. A query result is only then guaranteed to be sorted when you apply ORDER BY, which you don't. Your query boils down to a mere
SELECT * FROM sliders;
Even if you happen to get the rows in the desired order with your query now, this can be already different the next time you run it.
(Apart from this, you are applying LIMIT 1 without an ORDER BY clause, which just picks a record arbitrarily. You could get any of the image urls with the first subquery.)
You need an ORDER BY clause in which you must check whether the row's ID is the first image or the first video:
SELECT *
FROM sliders
ORDER BY
id = (SELECT MIN(id) FROM sliders WHERE image_url IS NOT NULL) DESC,
id = (SELECT MIN(id) FROM sliders WHERE video_url IS NOT NULL) DESC,
id;
(This makes use of MySQL's true = 1, false = 0. By sorting in descending order, we get true before false.)
One method in MySQL is to use variables:
select s.*
from (select s.*,
(case when image_url is not null then #rn_i := #rn_i + 1 end) as rn_i,
(case when video_url is not null then #rn_v := #rn_v + 1 end) as rn_v,
from sliders cross join
(select #rn_i := 0, #rn_v := 0) params
order by id
) s
order by (rn_i = 1) desc, (rn_v = 1) desc, id asc;

MySQL modify query to get a specific row

I have the following MySQL query:
SELECT
#rownum:=#rownum+1 rank,
userID,
xpTotal
from users xpTotal, (SELECT #rownum:=0) r
WHERE username != '' && bot = 'false'
ORDER BY xpTotal DESC
Which results in something like this:
rank | userID | xpTotal
--------------------------------
1 | 2934729447 | 52873
2 | 8523954935 | 33465
3 | 4576456556 | 13466
4 | 2341234555 | 04244
5 | 3453565334 | 02297
How can I modify my query to get the rank of say ID 2341234555? Meaning, in this case, the query would only output the 4th row.
You need to wrap your current in a subquery, otherwise if you add it in the condition in the current WHERE clause, the rank would always be 1.
SELECT *
FROM
(
SELECT
#rownum:=#rownum+1 rank,
userID,
xpTotal
FROM users xpTotal, (SELECT #rownum:=0) r
WHERE username != '' && bot = 'false'
) a
WHERE a.UserID = '2341234555';

Enumerate records sequentially, grouped and by date, in MySQL

This seems like such a simple question and I terrified that I might be bashed with the duplicate question hammer, but here's what I have:
ID Date
1 1/11/01
1 3/3/03
1 2/22/02
2 1/11/01
2 2/22/02
All I need to do is enumerate the records, based on the date, and grouped by ID! As such:
ID Date Num
1 1/11/01 1
1 3/3/03 3
1 2/22/02 2
2 1/11/01 1
2 2/22/02 2
This is very similar to this question, but it's not working for me. This would be great but it's not MySQL.
I've tried to use group by but it doesn't work, as in
SELECT ta.*, count(*) as Num
FROM temp_a ta
GROUP BY `ID` ORDER BY `ID`;
which clearly doesn't run since the GROUP BY always results to one value.
Any advice greatly appreciated.
Let's assume the table to be as follows:
CREATE TABLE q43381823(id INT, dt DATE);
INSERT INTO q43381823 VALUES
(1, '2001-01-11'),
(1, '2003-03-03'),
(1, '2002-02-22'),
(2, '2001-01-11'),
(2, '2002-02-22');
Then, one of the ways in which the query to get the desired output could be written is:
SELECT q.*,
CASE WHEN (
IF(#id != q.id, #rank := 0, #rank := #rank + 1)
) >=1 THEN #rank
ELSE #rank := 1
END as rank,
#id := q.id AS buffer_id
FROM q43381823 q
CROSS JOIN (
SELECT #rank:= 0,
#id := (SELECT q2.id FROM q43381823 AS q2 ORDER BY q2.id LIMIT 1)
) x
ORDER BY q.id, q.dt
Output:
id | dt | rank | buffer_id
-------------------------------------------------
1 | 2001-01-11 | 1 | 1
1 | 2002-02-22 | 2 | 1
1 | 2003-03-03 | 3 | 1
2 | 2001-01-11 | 1 | 2
2 | 2002-02-22 | 2 | 2
You may please ignore the buffer_id column from the output - it's irrelevant to the result, but required for the resetting of rank.
SQL Fiddle Demo
Explanation:
#id variable keeps track of every id in the row, based on the sorted order of the output. In the initial iteration, we set it to id of the first record that may be obtained in the final result. See sub-query SELECT q2.id FROM q43381823 AS q2 ORDER BY q2.id LIMIT 1
#rank is set to 0 initially and is by default incremented for every subsequent row in the result set. However, when the id changes, we reset it back to 1. Please see the CASE - WHEN - ELSE construct in the query for this.
The final output is sorted first by id and then by dt. This ensures that #rank is set incrementally for every subsequent dt field within the same id, but gets reset to 1 whenever a new id group begins to show up in the result set.