Here is the source table:
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
Here is the result table:
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
I have the MySQL version query, how to convert it to SQL server version? I tried to do the declare but I have no idea how to update the value of the variables.
SELECT Score, ranking AS Rank
FROM
(
SELECT
Score,
CASE
WHEN #dummy = Score
THEN #ranking := #ranking
ELSE #ranking := #ranking + 1
END as ranking,
#dummy := Score
FROM Scores, (SELECT #ranking := 0, #dummy := -1) init
ORDER BY Score DESC
)AS Result
Your code is a MySQL work-around for the ANSI standard DENSE_RANK() function (as explained by Sean Lange in a comment). The code simply looks like:
SELECT s.*, DENSE_RANK() OVER (ORDER BY Score DESC) as rank
FROM Scores s
ORDER BY Score DESC;
Incidentally, the MySQL code itself is not really accurate. The following is much safer:
SELECT Score, ranking AS Rank
FROM (SELECT Score,
(#rn := if(#dummy = Score, #rn,
if(#dummy := Score, #rn + 1, #rn + 1)
)
) as ranking
FROM Scores CROSS JOIN
(SELECT #rn := 0, #dummy := -1) init
ORDER BY Score DESC
) s;
The key difference is that all the assignments to variables occur in a single expression. MySQL does not guarantee the order of evaluations of expressions in a SELECT, so you should not use #dummy in one expression and then assign it in another.
SQL Server doesn't support variables used in that manner, so the syntax doesn't translate. Scalar variables are set once by a query, not set once for each row of the result set. MySQL's syntax here is a hack to get analytic-function-like behavior without analytic function support. You should just use:
SELECT Score,
DENSE_RANK() OVER(ORDER BY Score DESC) AS Rank
FROM Scores
ORDER BY Score DESC;
If you insist on not using DENSE_RANK(), you can use the SQL Server syntax from SQL Server 2000:
SELECT s1.Score,
(SELECT COUNT(DISTINCT s2.Score) FROM Scores s2 WHERE s1.Score <= s2.Score) AS Ranking
FROM Scores s1
ORDER BY s1.Score DESC;
Related
I want to rank the following Scores table where same scores will have same rank.
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 4.00 |
| 3 | 4.00 |
| 4 | 3.50 |
+----+-------+
Can someone help why is the following query throwing a syntax error ? Also, is the logic incorrect?
Code:
select Score, RANK() OVER (order by Score desc) as rank
from Scores
order by Score desc;
Error message:
Line 1: SyntaxError: near '(order by Score desc) as rank
from Scores
order by Score desc'
E.g.: (And assuming you want ranks of 1 and 2 rather than 1 and 3)
SELECT x.*
, CASE WHEN #prev = score THEN #i:=#i ELSE #i:=#i+1 END rank
, #prev:=score
FROM my_table x
, (SELECT #prev:=null,#i:=0) vars
ORDER
BY score DESC
, id;
For sports ranking, you can do this:
SELECT a.*
, FIND_IN_SET(score,
(SELECT GROUP_CONCAT(score ORDER BY score DESC)
-- inclusion of DISTINCT here will output as above
FROM my_table)
) x
FROM my_table a;
But there are certain caveats associated with this solution
I am trying to rank my students by their points that I've calculated before
but the problem is if students have same points they both should be in same rank
E.g
Student 1 has full points
Student 2 has full points
they both have to be rank as 1;
Here an example of my database
the query I am trying to do is (just for select then I can insert the values to my column)
SELECT a.points
count(b.points)+1 as rank
FROM examresults a left join examresults b on a.points>b.points
group by a.points;
Edit for being more clear:
Student 1 points 80
Student 2 points 77.5
Student 3 points 77.5
Student 4 points 77
their ranks should be like
Student 1 Rank 1
Student 2 Rank 2
Student 3 Rank 2
Student 4 Rank 3
my current query returns a values like
As it is missing the third rank. (because second rank has 2 values)
This is just a fix of Gordon solution using variables. The thing is your rank function isnt the way rank should work. (student 4 should be rank 4)
SQL Fiddle Demo You can add more student to improve the testing.
select er.*,
(#rank := if(#points = points,
#rank,
if(#points := points,
#rank + 1,
#rank + 1
)
)
) as ranking
from students er cross join
(select #rank := 0, #points := -1) params
order by points desc;
OUTPUT
| id | points | ranking |
|----|--------|---------|
| 1 | 80 | 1 |
| 2 | 78 | 2 |
| 3 | 78 | 2 |
| 4 | 77 | 3 |
| 5 | 66 | 4 |
| 6 | 66 | 4 |
| 7 | 66 | 4 |
| 8 | 15 | 5 |
You want a real rank, which is calculated by the ANSI standard rank() function. You can implement this in MySQL using this logic:
select er.*,
(select 1 + count(*)
from examresults er2
where er2.points > er.points
) as ranking
from exampleresults er;
For larger tables, you can do this with variables, but it is a rather awkward:
select er.*,
(#rank := if(#rn := #rn + 1 -- increment row number
if(#points = points, #rank, -- do not increment rank
if(#points := points, -- set #points
#rn, #rn -- otherwise use row number
)
)
)
) as ranking
from examresults er cross join
(select #rn := 0, #rank := 0, #points := -1) params
order by points desc;
this query achieve what do you want:
SELECT student_id , points, (select count(distinct(points))+1 as rank
from examresults internal
where internal.points > external.points order by points)
FROM examresults external
group by student_id
I have the following ranking system.
SET #1=0;
SELECT id, username, magic_xp, #i:=#i+1 AS rank
FROM hs_users
ORDER
BY magic_xp DESC;
hs_users
id username magic_xp rank
988 5hapescape 14926854 1
737 Ozan 13034431 2
989 Kurt 13034431 3
6 LEGACY 0 4
11 Bobby 0 5
276 Bobby123 0 6
345 Mynamesjason 0 7
450 Demon Spawn 0 8
987 Satan 0 9
As you see I have 2 users have the same xp.
I want to make them both have rank = 2 and the rest should follow from 3.
How can I group them like this?
| username | magic_xp | rank |
| ---------- + -------- + ---- |
| ShapeScape | 1000 | 1 |
| Kurt | 100 | 2 |
| Ozan | 100 | 2 |
| Legacy | 10 | 3 |
In MySQL, the most efficient way is to use variables:
select t.*,
(#rank := if(#magic_xp = magic_xp, #rank,
if(#magic_xp := magic_xp, #rank + 1, #rank + 1)
)
) as rank
from table t cross join
(select #rank := 0, #magic_xp := NULL) params
order by magic_xp desc;
Note the complicated expression for the variables. The assignment of both variables is in a single expression. This is on purpose. MySQL does not guarantee the order of assignment of expressions in a SELECT, and sometimes, it does not even evaluate them in order. A single expression is the safe way to do this logic.
A more standard approach in SQL is to use a correlated subquery:
select t.*,
(select count(distinct t2.magic_xp)
from table t2
where t2.magic_xp >= t.magic_xp
) as rank
from table t;
query
set #i := 0;
set #lagxp := null;
select id, username, magic_xp,
#i := if(#lagxp = magic_xp, #i,
if(#lagxp := magic_xp, #i + 1, #i + 1)) as rank
from hs_users
order by magic_xp desc
;
or
SELECT id, username, magic_xp,
IF (#score=hs_users.magic_xp, #rank:=#rank, #rank:=#rank+1) as rank,
#score:=hs_users.magic_xp score
FROM hs_users, (SELECT #score:=0, #rank:=0) r
ORDER BY magic_xp DESC;
output
+-----+------------+----------+------+----------+
| id | username | magic_xp | rank | lagxp |
+-----+------------+----------+------+----------+
| 988 | Shapescape | 14926894 | 1 | 14926894 |
| 737 | Ozan | 13034431 | 2 | 13034431 |
| 989 | Kurt | 13034431 | 2 | 13034431 |
| 6 | Legacy | 0 | 3 | 0 |
+-----+------------+----------+------+----------+
sqlfiddle
Sound the solution :)
SELECT id, username, magic_xp,
IF (#score=hs_users.magic_xp, #rank:=#rank, #rank:=#rank+1) as rank,
#score:=hs_users.magic_xp score
FROM hs_users, (SELECT #score:=0, #rank:=0) r
ORDER BY magic_xp DESC;
Thanks to #amdixon
select
#rank:=if(magic_xp=#prev_magic_xp,#rank,#rank+1) as rank,
username,
magic_xp,
#prev_magic_xp:=magic_xp as prev_magic_xp
from user,(select #rank:=0,#prev_magic_xp="") t
order by magic_xp desc
For your reference: http://sqlfiddle.com/#!9/09bb3/2
I have a problem with my Query. I want to select data from my Ranking Query.
My query output is Perfect Like:
------------------------------
Rank | ID | Username | Value
-------------------------------
1 | 5 | Julian | 5000
2 | 2 | Masha | 2400
3 | 4 | Misha | 2300
4 | 1 | Jackson | 1900
5 | 9 | Beruang | 400
-------------------------------
But when I select ID = 4, the output like this:
------------------------------
Rank | ID | Username | Value
-------------------------------
***1*** | 4 | Misha | 2300
-------------------------------
The output of ranking is 1, not 3.
My Query is :
SELECT #curRank := #curRank + 1 AS rank,
a.id, a.username
FROM partimer a CROSS JOIN
(SELECT #curRank := 0) vars
# WHERE a.id = 4
ORDER By id;
If Rank is dinamically computed, you could do this:
SELECT *
FROM (
SELECT #curRank := #curRank + 1 AS rank
, a.id
, a.username
FROM partimer a
CROSS JOIN (SELECT #curRank := 0) vars
ORDER BY value
) p
WHERE p.id = 4;
This way, you store temporary table with rank, and then select from it.
you should like this
SELECT *
FROM (
...{your Query}...
) qry
WHERE qry.id = 4
Your rank is calculated dynamically in the query. The issue here is that these dynamic calculations are applied after the where clause. In other words, when MySQL executes your query, it first filters out all the rows that adhere to the where clause, and only then applies the rank calculation. In the given query, the row with id=4 is indeed the 1st row between all the rows that adhere to the where clause.
One way to get your desired behavior is to perform the original query first and only then filter the results by using this query as a subquery and applying the where clause to the outer query:
SELECT *
FROM (SELECT #curRank := #curRank + 1 AS rank, a.id, a.username
FROM partimer a
CROSS JOIN (SELECT #curRank := 0) vars
ORDER By id) t
WHERE id = 4
I have a table like:
time | status
1390836600 | 1
1390836605 | 1
1390836610 | 0
1390836615 | 0
1390836620 | 1
1390836625 | 1
1390836630 | 1
I need to output the data "grouped" by the status, and sorted by time. The trick is that I need the groupings in chunks for each time the status changes, with the fields: MIN(time), status
So for the example data above I'd need an output like
MIN(time) | status
1390836600 | 1
1390836610 | 0
1390836620 | 1
This is not the behaviour of GROUP BY, which would just group ALL rows with the same status and only output 2 rows. But is something like this possible?
This (grouping of continuous ranges) is called gaps-and-islands problem and can be effectively solved by using analytic functions (specifically ROW_NUMBER()) which MySQL still has no support for.
But you can emulate ROW_NUMBER() with session variables in the following way
SELECT MIN(time) time, status
FROM
(
SELECT time, status,
#n := #n + 1 rnum,
#g := IF(status = #s, #g + 1, 1) rnum2,
#s := status
FROM table1 CROSS JOIN (SELECT #n := 0, #g := 0, #s := NULL) i
ORDER BY time
) q
GROUP BY rnum - rnum2
Output:
| TIME | STATUS |
|------------|--------|
| 1390836600 | 1 |
| 1390836610 | 0 |
| 1390836620 | 1 |
Here is a SQLFiddle demo