【问题标题】:MySQL dense rank for each group/partition每个组/分区的 MySQL 密集排名
【发布时间】:2019-07-31 21:49:20
【问题描述】:

我正在尝试构建一种密集等级的功能,但不完全是密集等级。我想对每个组进行排名,但在每个组中,我想保持相同值的排名相同。我编写了以下查询,它为我提供了密集等级的功能

SELECT  id, item_code
    @curRank := @curRank + 1 AS rank
FROM table t, (SELECT @curRank := 0) r
WHERE <several filters>

然而,我想要这个

我找到的最接近的问题是这个MySQL give a rank to each group

请帮忙。

【问题讨论】:

  • 您使用的是什么版本的 MySQL,如果您还没有使用,是否有机会升级到 MySQL 8+?
  • 另外,对于1803 id 组,为什么131677 排在33373 之前?
  • 你的mysql版本是多少?
  • @TimBiegeleisen 我正在使用版本,有可能移至 8,但未确认。我知道版本 8 具有排名功能。其次,item_code 列的顺序并不重要,因为代码并不总是数字

标签: mysql sql group-by window-functions dense-rank


【解决方案1】:

您可以使用复合主键中的 AUTO_INCREMENT rank 列创建(临时)MyISAM 表。这样,当您使用不同的 (id, item_code) 组合填充表格时,rank 将每个 ID 有一个序列。然后,您可以将该表与原始数据连接起来。

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

如果你想在单个查询中使用它,你可以试试这个:

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

请注意,未定义 SQL 语句中操作的评估顺序。因此,当您在同一条语句中读取和写入用户变量时,您会传递实现细节。

在 MySQL 8 或 MariaDB 10.2 中:

SELECT  id, item_code,
    DENSE_RANK() OVER (PARTITION BY id ORDER BY item_code) as `rank`
FROM test t
-- WHERE <several filters>

Demo

【讨论】:

  • 在最初发布的问题中,我试图将每个组的排名重新设置为 1。您的解决方案是增加排名,这正是密集排名所做的,但我的要求有点不同。请查看问题中的第二张图片。
  • @Ali - 您可以使用 Barmar 的代码。它适用于一个小修复。
【解决方案2】:

使用变量的正确方法是:

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;

两件事:

  • 在一个表达式中分配一个变量并在另一个表达式中使用它可能会导致问题。您需要在单个表达式中分配和使用变量。 MySQL 不保证表达式的求值顺序。
  • ORDER BY 非常重要。

您也可以使用相关子查询来表达这一点:

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>

【讨论】:

  • 第一个解决方案有效,但它会不断增加每个组的排名。我最初要求的是我想将每个组的排名重置为 1,如第二张图片所示,其中每个组的排名从 1 开始。
  • @阿里。 . .我误读了结果。这使得逻辑有点复杂,但仍然可以使用变量。
  • 已尝试但出现错误 - #1064 - 您的 SQL 语法有错误;检查与您的 MySQL 服务器版本相对应的手册,以获取在 if(@id := id, if(@ic := @it' at line 4 附近使用的正确语法
【解决方案3】:

您需要仅在item_code 更改时增加@cur_rank,并在id 更改时将其设置回1。

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

【讨论】:

  • @curItem = item_code 应该是 SELECT 子句末尾的 @curItem := item_code。
  • @PaulSpiegel 我看不出有什么区别。
  • =(比较)和:=(设置)
  • @PaulSpiegel ?
  • @Barmar 它返回 1 作为每一行的排名
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-17
  • 2014-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多