MySQL плотный ранг для каждой группы / раздела - PullRequest
1 голос
/ 10 марта 2019

Я пытаюсь создать функциональность плотного ранга, но не совсем плотного ранга. Я хочу ранжировать каждую группу, но внутри каждой группы я хочу сохранить ранг одинаковым для одинаковых значений. Я написал следующий запрос, который дает мне функциональность с плотным рангом

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

enter image description here

Принимая во внимание, я хочу это

enter image description here

Самый близкий вопрос, который я нашел, - это MySQL дает оценку каждой группе

Пожалуйста, помогите.

Ответы [ 3 ]

2 голосов
/ 10 марта 2019

Правильный метод с использованием переменных:

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>
2 голосов
/ 10 марта 2019

Вы можете создать (временную) MyISAM таблицу со столбцом AUTO_INCREMENT rank в составном ПЕРВИЧНОМ КЛЮЧЕ.Таким образом, rank будет иметь одну последовательность на ID, когда вы заполняете таблицу различными (id, item_code) комбинациями.Затем вы можете объединить эту таблицу с исходными данными.

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>

Демонстрация

Если вы хотите использовать ее в одном запросе, вы можете попробовать это:

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

Демонстрация

Имейте в виду, что порядок вычисления операций в операторе 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>

Демо

1 голос
/ 10 марта 2019

Вам нужно увеличивать @cur_rank только при изменении item_code и возвращать значение 1 при изменении id.

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
...