问题
I am trying to build a dense rank kind of functionality but not exactly dense rank. I want to rank each group but within each group I want to keep the rank same for same values. I've written the following query which gives me a dense rank kind of functionality
SELECT id, item_code
@curRank := @curRank + 1 AS rank
FROM table t, (SELECT @curRank := 0) r
WHERE <several filters>
Whereas, I want this
The closest question I found is this one MySQL give a rank to each group
Please help.
回答1:
You can create a (temporary) MyISAM table with an AUTO_INCREMENT rank
column in a composite PRIMARY KEY. This way rank
will have one sequence per ID
when you fill the table with distinct (id, item_code)
combinations. You can then join that table with the original data.
CREATE TEMPORARY TABLE tmp_rank(
id INT,
item_code varchar(50),
rank INT AUTO_INCREMENT,
PRIMARY KEY (id, rank)
) engine=MyISAM
SELECT DISTINCT NULL as rank, id, item_code
FROM test t
-- WHERE <several filters>
ORDER BY id, item_code
;
SELECT t.*, x.rank
FROM tmp_rank x
JOIN test t USING(id, item_code)
-- WHERE <several filters>
Demo
If you want it in a single query, you can try this one:
SELECT id, item_code,
CASE
WHEN id = @curId AND item_code = @curCode
THEN @curRank
WHEN id <> @curId THEN @curRank := 1
ELSE @curRank := @curRank + 1
END AS rank,
@curId := id,
@curCode := item_code
FROM test t
CROSS JOIN (SELECT
@curRank := 0,
cast(@curCode := null as signed),
cast(@curId := NULL as char)
) r
#WHERE <several filters>
ORDER BY id, item_code
Demo
Be aware, that the evaluation order of operations in an SQL statement is not defined. So when you read and write a user variable in the same statement, you relay on implementation details.
In MySQL 8 or MariaDB 10.2 it would be:
SELECT id, item_code,
DENSE_RANK() OVER (PARTITION BY id ORDER BY item_code) as `rank`
FROM test t
-- WHERE <several filters>
Demo
回答2:
The correct method using variables is:
SELECT id,item_code,
(@rn := if(@id = id,
if(@ic = item_code, @rn, -- do not increment
if(@ic := item_code, @rn + 1, @rn + 1)
),
if(@id := id,
if(@ic := @item_code, 1, 1
), 1
if(@ic := @item_code, 1, 1
)
)
)
) as dense_rank
FROM table t CROSS JOIN
(SELECT @rn := 0, @id := '', @ic := '') params
WHERE <several filters>
ORDER BY item_code;
Two things:
- Assigning a variable in one expression and using it in another can lead to issues. You need to assign and use the variables in a single expression. MySQL does not guarantee the order of evaluation of expressions.
- The
ORDER BY
is really important.
You can also express this using a correlated subquery:
SELECT id, item_code
(SELECT COUNT(DISTINCT item_code)
FROM table t2
WHERE . . . AND
t2.item_code <= t.item_code
) as rank
FROM table t
WHERE <several filters>
回答3:
You need to increment @cur_rank
only when the item_code
changes, and set it back to 1
when the id
changes.
SELECT id, item_code,
@curRank := IF(@curId = id,
IF(@curItem = item_code, @curRank, @curRank + 1),
1) AS rank,
@curId := id, @curItem := item_code
FROM table t, (SELECT @curRank := 0, @curId := 0, @curItem := null) r
WHERE <several filters>
ORDER BY id, item_code
来源:https://stackoverflow.com/questions/55087324/mysql-dense-rank-for-each-group-partition