MySQL dense rank for each group/partition

岁酱吖の 提交于 2021-02-19 08:57:39

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!