I have a MySQL table which contains statistics about function usage for a program. What I retrieve from it basically looks like this (top 15 total here) :
SELECT function_id, data_timer, SUM( data_counter ) total
FROM data
GROUP BY function_id
ORDER BY total DESC
+-------------+------------+-------+
| function_id | data_timer | total |
+-------------+------------+-------+
| 56 | 567 | 4389 |
| 23 | 7880 | 1267 |
| 7 | 145 | 812 |
| ... | ... | ... |
+-------------+------------+-------+
Since those results are used in a website module where the user can select which column will be used to ORDER BY as well as between ASC and DESC, I needed to retrieve the rank of each row of the results.
With the help of this question, I was able to assign a rank to each row of the results :
SET #rank = 0;
SELECT #rank:=#rank+1 AS rank, function_id, data_timer, SUM( data_counter ) total
FROM data
WHERE client_id = 2
GROUP BY function_id
ORDER BY total DESC
+------+-------------+------------+-------+
| rank | function_id | data_timer | total |
+------+-------------+------------+-------+
| 1 | 56 | 567 | 4389 |
| 2 | 23 | 7880 | 1267 |
| 3 | 7 | 145 | 812 |
| ... | ... | ... | ... |
+------+-------------+------------+-------+
I am now having some difficulties trying to invert this table, meaning I would like to have the results sorted with the least used function first. Something like this (supposing there are 76 functions) :
+------+-------------+------------+-------+
| rank | function_id | data_timer | total |
+------+-------------+------------+-------+
| 76 | 44 | 346 | 1 |
| 75 | 2 | 3980 | 4 |
| 74 | 13 | 612 | 7 |
| ... | ... | ... | ... |
+------+-------------+------------+-------+
Here is my SQL query attempt :
SELECT rank, function_id, data_timer, total
FROM
(
SET #rank = 0;
SELECT #rank:=#rank+1 AS rank, function_id, data_timer, SUM( data_counter ) total
FROM data
WHERE client_id = 2
GROUP BY function_id
ORDER BY total DESC
)
ORDER BY rank DESC
It keeps popping me this :
#1064 - 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
'SET #rank = 0' at line 4
Since I'm not too skilled with SQL, I guess I'm missing something obvious.
Any help will be gladly appreciated, thanks!
You are trying to assign a variable inside of your sub-query. This won't work. Move the assignment outside of your sub-query and it should run.
SET #rank = 0;
SELECT rank, function_id, data_timer, total
FROM
(
SELECT #rank:=#rank+1 AS rank, function_id, data_timer, SUM( data_counter ) total
FROM data
WHERE client_id = 2
GROUP BY function_id
ORDER BY total DESC
)
ORDER BY rank DESC
Another option is to initialize your #rank variable in your query instead of a separate statement:
SELECT rank, function_id, data_timer, total
FROM
(
SELECT #rank:=#rank+1 AS rank, function_id, data_timer, SUM( data_counter ) total
FROM data,
(SELECT #rank := 0 ) r
WHERE client_id = 2
GROUP BY function_id
ORDER BY total DESC
) r
ORDER BY rank DESC
Condensed SQL Fiddle Demo
Related
I have the following column names:
customer_email
increment_id
other_id (psuedo name)
created_at
increment_id and other_id will be unique, customer_email will have duplicates. As the results are returned I want to know what number of occurance of the email it is.
For each row, I want to know how many times thecustomer_email value has shown up so far. There will be an order by clause at the end for the created_at field and I plan to also add a where clause of where occurrences < 2
I am querying > 5 million rows but performance isn't too important because I'll be running this as a report on a read-replica database from production. In my use case, I will sacrifice performance for robustness.
| customer_email | incremenet_id | other_id | created_at | occurances <- I want this |
|----------------|---------------|----------|---------------------|---------------------------|
| joe#test.com | 1 | 81 | 2019-11-00 00:00:00 | 1 |
| sue#test.com | 2 | 82 | 2019-11-00 00:01:00 | 1 |
| bill#test.com | 3 | 83 | 2019-11-00 00:02:00 | 1 |
| joe#test.com | 4 | 84 | 2019-11-00 00:03:00 | 2 |
| mike#test.com | 5 | 85 | 2019-11-00 00:04:00 | 1 |
| sue#test.com | 6 | 86 | 2019-11-00 00:05:00 | 2 |
| joe#test.com | 7 | 87 | 2019-11-00 00:06:00 | 3 |
You can use variables in earlier versions of MySQL:
select t.*,
(#rn := if(#ce = customer_email, #rn + 1,
if(#ce := customer_email, 1, 1)
)
) as occurrences
from (select t.*
from t
order by customer_email, created_at
) t cross join
(select #ce := '', #rn := 0) params;
In MyQL 8+, I would recommend row_number():
select t.*,
row_number() over (partition by customer_email order by created_at) as occurrences
from t;
If you are running MySQL 8.0, you can just do a window count:
select
t.*,
count(*) over(partition by customer_email order by created_at) occurences
from mytable t
You don't need an order by clause at the end of the query for this to work (but you need one if you want to order the results).
If you need to filter on the results of the window count, an additional level is needed, since window functions cannot be used in the where clause of a query:
select *
from (
select
t.*,
count(*) over(partition by customer_email order by created_at) occurences
from mytable t
) t
where occurences < 2
I have a table (called users) I need rank of users based on their score but I want rank on the bases of users max score.
+-----------+------------+
| User_id | Score |
+-----------+------------+
| 1 | 12258 |
| 1 | 112 |
| 2 | 9678 |
| 5 | 9678 |
| 3 | 689206 |
| 3 | 1868 |
Expect result
+-----------+------------+---------+
| User_id | Score | Rank |
+-----------+------------+---------+
| 3 | 689206 | 1 |
| 1 | 12258 | 2 |
| 2 | 9678 | 3 |
| 5 | 9678 | 3 |
You are looking for DENSE_RANK, But it supports mysql version higher than 8.0
use correlated-subquery to get max value by each User_id
use two variables one to store rank another to store previous value to make the DENSE_RANK number.
look like this.
CREATE TABLE T(
User_id int,
Score int
);
insert into t values (1,12258);
insert into t values (1,112);
insert into t values (2,9678);
insert into t values (5,9678);
insert into t values (3,689206);
insert into t values (3,1868);
Query 1:
SELECT User_id,Score,Rank
FROM (
SELECT User_id,
Score,
#rank :=IF(#previous = t1.score, #rank, #rank + 1) Rank,
#previous := t1.Score
FROM T t1 CROSS JOIN (SELECT #Rank := 0,#previous := 0) r
WHERE t1.Score =
(
SELECT MAX(Score)
FROM T tt
WHERE t1.User_id = tt.User_id
)
ORDER BY Score desc
) t1
Results:
| User_id | Score | Rank |
|---------|--------|------|
| 3 | 689206 | 1 |
| 1 | 12258 | 2 |
| 2 | 9678 | 3 |
| 5 | 9678 | 3 |
Another trick in MySql 5.7 to calculate a DENSE_RANK (like in MySql 8) is to use a CASE WHEN with the variable assignments in it.
SELECT User_id, MaxScore AS Score,
CASE
WHEN MaxScore = #prevScore THEN #rnk
WHEN #prevScore := MaxScore THEN #rnk := #rnk+1
ELSE #rnk := #rnk+1
END AS Rank
FROM
(
SELECT User_id, MAX(Score) AS MaxScore
FROM YourTable
GROUP BY User_id
ORDER BY MaxScore DESC, User_id
) AS q
CROSS JOIN (SELECT #rnk := 0, #prevScore := null) AS vars
You can test it here on rextester.
I have a table like this
+-----+-------+
| pid | score |
+-----+-------+
| 1 | 120|
| 2 | 130|
| 3 | 100|
| 1 | 120|
| 2 | 130|
| 3 | 100|
+-----+-------+
I am trying to get get my users ranking. Currently I am using this query,
SELECT pid
, SUM(score) AS total_score
FROM score
GROUP
BY pid
ORDER
BY total_score DESC
I am getting a sorted list by score with this query but not getting their position. I want something like this
+-----+-------+------+
| pid | score | rank |
+-----+-------+------+
| 2 | 260| 1 |
| 1 | 240| 2 |
| 3 | 200| 3 |
+-----+-------+------+
And can I get this position for a specific user? Like
+-----+-------+------+
| pid | score | rank |
+-----+-------+------+
| 1 | 240| 2 |
+-----+-------+------+
I am using MySQL 5.7.21. Can you please help?
If you have the chance to be in Mysql 8.0.2 or more you have the function rank.
Else You can use a variable to set the rank:
SELECT pid,
total_score,
#curRank := #curRank + 1 AS rank
FROM
(SELECT pid,
sum(score) AS total_score
FROM score
GROUP BY pid
ORDER BY total_score DESC) datas, (SELECT #curRank := 0) r
You can filter after to get only the result for the pid you want
Yes, you can use LIMIT like:
SELECT pid, SUM(score) AS total_score
FROM score
GROUP BY pid
ORDER BY total_score DESC
LIMIT 1
I have very much similar kind of requirement as described in this question.
Rank users in mysql by their points
The only difference is in my data. The above problem has the data where table has only row per student. But in my case there may be a possibility that table contains multiple rows for a single student like this
Student 1 points 80
Student 2 points 77.5
Student 2 points 4.5
Student 3 points 77
Student 4 points 77
So now rank should be calculated based on the SUM of points (total) that user has. So in this case result would be.
Student 2 Rank 1 with 82 points
Student 1 Rank 2 with 80 points
Student 3 Rank 3 with 77 points
Student 4 Rank 3 with 77 points
SQL Fiddle for data
I tried couple of things with the solution of above question but couldn't get the result. Any help would be appreciated.
Using the same query in my previous answer just change the table student for a subquery to combine all records of every student
change [student er] for
(SELECT `id`, SUM(`points`) as `points`
FROM students
GROUP BY `id`) er
SQL DEMO
select er.*,
(#rank := if(#points = points,
#rank,
if(#points := points,
#rank + 1,
#rank + 1
)
)
) as ranking
from (SELECT `id`, SUM(`points`) as `points`
FROM students
GROUP BY `id`) er cross join
(select #rank := 0, #points := -1) params
order by points desc;
OUTPUT
| id | points | ranking |
|----|--------|---------|
| 5 | 91 | 1 |
| 6 | 81 | 2 |
| 1 | 80 | 3 |
| 2 | 78 | 4 |
| 3 | 78 | 4 |
| 4 | 77 | 5 |
| 7 | 66 | 6 |
| 8 | 15 | 7 |
Try this:
select id, points, #row := ifnull(#row, 0) + diff rank
from (select *, ifnull(#prev, 0) != points diff, #prev := points
from (select id, sum(points) points
from students
group by 1
order by 2 desc) x) y
See SQLFiddle
EDITED:
(This should work)
SELECT I.Id, I.Points, Rk.Rank
FROM
(SELECT Id, Points, #Rk := #Rk+1 As Rank
FROM (SELECT id, SUM(points) AS Points
FROM students
GROUP BY id
ORDER BY Points DESC) As T,
(SELECT #Rk := 0) AS Rk) As I
INNER JOIN
(SELECT *
FROM (
SELECT Id, Points, #Rk2 := #Rk2+1 As Rank
FROM (SELECT id, SUM(points) AS Points
FROM students
GROUP BY id
ORDER BY Points DESC) As T1,
(SELECT #Rk2 := 0) AS Rk) AS T2
GROUP BY Points) As Rk
USING(Points)
The output will be:
| Id | Points | Rank |
|----|--------|---------|
| 5 | 91 | 1 |
| 6 | 81 | 2 |
| 1 | 80 | 3 |
| 2 | 78 | 4 |
| 3 | 78 | 4 |
| 4 | 77 | 6 |
| 7 | 66 | 7 |
| 8 | 15 | 8 |
After two Ids in 4th position you'll get the 6th position because 5 Ids are before of the 6th.
Next weekend we're having a competition with 3 qualifications a semifinal and a final. Only the best 15 participants could compete in the semifinal. Only the best 6 compete in the Finals.
in the qualifications you get a score from 0 to 100 for each qualification
I'm looking to find a way to select the contesters for the semi-final. This should be based on (rank of qualification1) * (rank of qualification2) * (rank of qualification3)
so i need something like:
select id, name, ((.... as RANK_OF_SCORE_1) * (.. as RANK_OF_SCORE_2) * (... as RANK_OF_SCORE_3)) as qualification_score from participants order by qualification_score desc limit 15
but of course this is not valid mySQL.
Besides this problem if tho contesters have the same score, they should be both included in the semi-finals even if this exceeds the maximum of 15.
For the finals, we would like to select the best 6 of the semi-final scores. If 2 scores are the same we would like to select on the qualifications..
option 1 : use postgres, which support windowing functions (namely RANK() and DENSE_RANK())
SELECT user_id, score, rank() over (order by score desc) from scores;
Time : 0.0014 s
option 2 : use a self- join : the rank of a user with score X is (1 +the count(*) of users with score less than X) ; this is likely to be pretty slow
CREATE TABLE scores( user_id INT PRIMARY KEY, score INT, KEY(score) );
INSERT INTO scores SELECT id, rand()*100 FROM serie LIMIT 1000;
SELECT a.user_id, a.score, 1+count(b.user_id) AS rank
FROM scores a
LEFT JOIN scores b ON (b.score>a.score)
GROUP BY user_id ORDER BY rank;
+---------+-------+------+
| user_id | score | rank |
+---------+-------+------+
| 381 | 100 | 1 |
| 777 | 100 | 1 |
| 586 | 100 | 1 |
| 907 | 100 | 1 |
| 790 | 100 | 1 |
| 253 | 99 | 6 |
| 393 | 99 | 6 |
| 429 | 99 | 6 |
| 376 | 99 | 6 |
| 857 | 99 | 6 |
| 293 | 99 | 6 |
| 156 | 99 | 6 |
| 167 | 98 | 13 |
| 594 | 98 | 13 |
| 690 | 98 | 13 |
| 510 | 98 | 13 |
| 436 | 98 | 13 |
| 671 | 98 | 13 |
time 0.7s
option 3 :
SET #rownum = 0;
SELECT a.user_id, a.score, b.r FROM
scores a
JOIN (
SELECT score, min(r) AS r FROM (
SELECT user_id, score, #rownum:=#rownum+1 AS r
FROM scores ORDER BY score DESC
) foo GROUP BY score
) b USING (score)
ORDER BY r;
time : 0.0014 s
EDIT
SET #rownum1 = 0;
SET #rownum2 = 0;
SET #rownum3 = 0;
SELECT s.*, s1.r, s2.r, s3.r FROM
scores s
JOIN
(
SELECT score_1, min(r) AS r FROM (
SELECT score_1, #rownum1:=#rownum1+1 AS r
FROM scores ORDER BY score_1 DESC
) foo GROUP BY score_1
) s1 USING (score_1) JOIN (
SELECT score_2, min(r) AS r FROM (
SELECT score_2, #rownum2:=#rownum2+1 AS r
FROM scores ORDER BY score_2 DESC
) foo GROUP BY score_2
) s2 USING (score_2) JOIN (
SELECT score_3, min(r) AS r FROM (
SELECT score_3, #rownum3:=#rownum3+1 AS r
FROM scores ORDER BY score_3 DESC
) foo GROUP BY score_3
) s3 USING (score_3)
ORDER BY s1.r * s2.r * s3.r;