MySQL modify query to get a specific row - mysql

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

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 Query with the count, group by

Table: statistics
id | user | Message
----------------------
1 | user1 |message1
2 | user2 |message2
3 | user1 |message3
I am able to find the count of messages sent by each user using this query.
select user, count(*) from statistics group by user;
How to show message column data along with the count? For example
user | count | message
------------------------
user1| 2 |message1
|message3
user2| 1 |message2
You seem to want to show Count by user, which message sent by user.
If your mysql version didn't support window functions, you can do subquery to make row_number in select subquery, then only display rn=1 users and count
CREATE TABLE T(
id INT,
user VARCHAR(50),
Message VARCHAR(100)
);
INSERT INTO T VALUES(1,'user1' ,'message1');
INSERT INTO T VALUES(2,'user2' ,'message2');
INSERT INTO T VALUES(3,'user1' ,'message3');
Query 1:
SELECT (case when rn = 1 then user else '' end) 'users',
(case when rn = 1 then cnt else '' end) 'count',
message
FROM (
select
t1.user,
t2.cnt,
t1.message,
(SELECT COUNT(*) from t tt WHERE tt.user = t1.user and t1.id >= tt.id) rn
from T t1
join (
select user, count(*) cnt
from T
group by user
) t2 on t1.user = t2.user
) t1
order by user,message
Results:
| users | count | message |
|-------|-------|----------|
| user1 | 2 | message1 |
| | | message3 |
| user2 | 1 | message2 |
select user, count(*) as 'total' , group_concat(message) from statistics group by user;
You could join the result of your group by with the full table (or vice versa)?
Or, depending on what you want, you could use group_concat() using \n as separator.
Use Group_concat
select user, count(0) as ct,group_concat(Message) from statistics group by user;
This will give you message in csv format
NOTE: GROUP_CONCAT has size limit of 1024 characters by default in mysql.
For UTF it goes to 1024/3 and utfmb4 255(1024/4).
You can use group_concat_max_len global variable to set its max length as per need but take into account memory considerations on production environment
SET group_concat_max_len=100000000
Update:
You can use any separator in group_concat
Group_concat(Message SEPARATOR '----')
Try grouping with self-join:
select s1.user, s2.cnt, s1.message
from statistics s1
join (
select user, count(*) cnt
from statistics
group by user
) s2 on s1.user = s2.user

MySQL - Count Values occurring between other values

I'd like to count how many occurrences of a value happen before a specific value
Below is my starting table
+-----------------+--------------+------------+
| Id | Activity | Time |
+-----------------+--------------+------------+
| 1 | Click | 1392263852 |
| 2 | Error | 1392263853 |
| 3 | Finish | 1392263862 |
| 4 | Click | 1392263883 |
| 5 | Click | 1392263888 |
| 6 | Finish | 1392263952 |
+-----------------+--------------+------------+
I'd like to count how many clicks happen before a finish happens.
I've got a very roundabout way of doing it where I write a function to find the last
finished activity and query the clicks between the finishes.
Also repeat this for Error.
What I'd like to achieve is the below table
+-----------------+--------------+------------+--------------+------------+
| Id | Activity | Time | Clicks | Error |
+-----------------+--------------+------------+--------------+------------+
| 3 | Finish | 1392263862 | 1 | 1 |
| 6 | Finish | 1392263952 | 2 | 0 |
+-----------------+--------------+------------+--------------+------------+
This table is very long so I'm looking for an efficient solution.
If anyone has any ideas.
Thanks heaps!
This is a complicated problem. Here is an approach to solving it. The groups between the "finish" records need to be identified as being the same, by assigning a group identifier to them. This identifier can be calculated by counting the number of "finish" records with a larger id.
Once this is assigned, your results can be calculated using an aggregation.
The group identifier can be calculated using a correlated subquery:
select max(id) as id, 'Finish' as Activity, max(time) as Time,
sum(Activity = 'Clicks') as Clicks, sum(activity = 'Error') as Error
from (select s.*,
(select sum(s2.activity = 'Finish')
from starting s2
where s2.id >= s.id
) as FinishCount
from starting s
) s
group by FinishCount;
A version that leverages user(session) variables
SELECT MAX(id) id,
MAX(activity) activity,
MAX(time) time,
SUM(activity = 'Click') clicks,
SUM(activity = 'Error') error
FROM
(
SELECT t.*, #g := IF(activity <> 'Finish' AND #a = 'Finish', #g + 1, #g) g, #a := activity
FROM table1 t CROSS JOIN (SELECT #g := 0, #a := NULL) i
ORDER BY time
) q
GROUP BY g
Output:
| ID | ACTIVITY | TIME | CLICKS | ERROR |
|----|----------|------------|--------|-------|
| 3 | Finish | 1392263862 | 1 | 1 |
| 6 | Finish | 1392263952 | 2 | 0 |
Here is SQLFiddle demo
Try:
select x.id
, x.activity
, x.time
, sum(case when y.activity = 'Click' then 1 else 0 end) as clicks
, sum(case when y.activity = 'Error' then 1 else 0 end) as errors
from tbl x, tbl y
where x.activity = 'Finish'
and y.time < x.time
and (y.time > (select max(z.time) from tbl z where z.activity = 'Finish' and z.time < x.time)
or x.time = (select min(z.time) from tbl z where z.activity = 'Finish'))
group by x.id
, x.activity
, x.time
order by x.id
Here's another method of using variables, which is somewhat different to #peterm's:
SELECT
Id,
Activity,
Time,
Clicks,
Errors
FROM (
SELECT
t.*,
#clicks := #clicks + (activity = 'Click') AS Clicks,
#errors := #errors + (activity = 'Error') AS Errors,
#clicks := #clicks * (activity <> 'Finish'),
#errors := #errors * (activity <> 'Finish')
FROM
`starting` t
CROSS JOIN
(SELECT #clicks := 0, #errors := 0) i
ORDER BY
time
) AS s
WHERE Activity = 'Finish'
;
What's similar to Peter's query is that this one uses a subquery that's returning all the rows, setting some variables along the way and returning the variables' values as columns. That may be common to most methods that use variables, though, and that's where the similarity between these two queries ends.
The difference is in how the accumulated results are calculated. Here all the accumulation is done in the subquery, and the main query merely filters the derived dataset on Activity = 'Finish' to return the final result set. In contrast, the other query uses grouping and aggregation at the outer level to get the accumulated results, which may make it slower than mine in comparison.
At the same time Peter's suggestion is more easily scalable in terms of coding. If you happen to have to extend the number of activities to account for, his query would only need expansion in the form of adding one SUM(activity = '...') AS ... per new activity to the outer SELECT, whereas in my query you would need to add a variable and several expressions, as well as a column in the outer SELECT, per every new activity, which would bloat the resulting code much more quickly.