Как нумеровать последовательные записи на острове? - PullRequest
3 голосов
/ 15 марта 2019

У меня есть таблица, которая выглядит так:

group    date        color
  A      1-1-2019      R
  A      1-2-2019      Y
  B      1-1-2019      R
  B      1-2-2019      Y
  B      1-3-2019      Y
  B      1-4-2019      R
  B      1-5-2019      R
  B      1-6-2019      R

И это упорядочено по группе и дате. Мне нужен дополнительный столбец с порядковым номером подряд цвета 'R' для каждой группы.

Требуемый выход:

group    date        color    rank
  A      1-1-2019      R      1
  A      1-2-2019      Y      null
  B      1-1-2019      R      1
  B      1-2-2019      Y      null
  B      1-3-2019      Y      null
  B      1-4-2019      R      1
  B      1-5-2019      R      2
  B      1-6-2019      R      3

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

Неправильный запрос и вывод:

SELECT 
    *, 
    RANK() OVER (PARTITION BY group, color order by group, date) as rank
FROM table

group    date        color    rank
  A      1-1-2019      R      1
  A      1-2-2019      Y      null
  B      1-1-2019      R      1
  B      1-2-2019      Y      null
  B      1-3-2019      Y      null
  B      1-4-2019      R      2
  B      1-5-2019      R      3
  B      1-6-2019      R      4

Мне интересно, выполнимо ли это в SQL или я должен переключиться на другой язык (например, Python)?

Ответы [ 3 ]

3 голосов
/ 15 марта 2019

Вот как это можно сделать с помощью оконных функций. Сначала мы создаем CTE, у которого есть флаг, который указывает, что новая последовательность запущена, затем из этого мы генерируем тот, который считает порядковые номера. Наконец, мы подсчитываем строки в каждой последовательности, чтобы получить ранг:

WITH cte AS (SELECT `group`, date, color,
                    COALESCE(color = LAG(color) OVER(ORDER BY `group`, date), 0) AS samecolor
             FROM `table`),
sequences AS (SELECT `group`, date, color,
              SUM(samecolor = 0) OVER (ORDER BY `group`, date) AS seq_num
              FROM cte)
SELECT `group`, date, color,
       ROW_NUMBER() OVER (PARTITION BY seq_num) AS `rank`
FROM sequences
ORDER BY `group`, date

Выход:

group   date        color   rank
A       1-1-2019    R       1
A       1-2-2019    Y       1
B       1-1-2019    R       1
B       1-2-2019    Y       1
B       1-3-2019    Y       2
B       1-4-2019    R       1
B       1-5-2019    R       2
B       1-6-2019    R       3

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

Обратите внимание, что этот запрос также дает ранжирование для значений Y, если вы хотите, чтобы они были NULL, замените определение rank следующим:

CASE WHEN color = 'Y' THEN NULL
     ELSE ROW_NUMBER() OVER (PARTITION BY seq_num) 
     END AS `rank`
3 голосов
/ 15 марта 2019

Использование пользовательских переменных может сохранить ранг и предыдущие значения для получения результатов:

 CREATE TABLE tbl (
   `group` VARCHAR(1),
   `date` VARCHAR(8),
   `color` VARCHAR(1)
 );

 INSERT INTO tbl
   (`group`, `date`, `color`)
 VALUES
   ('A', '1-1-2019', 'R'),
   ('A', '1-2-2019', 'Y'),
   ('B', '1-1-2019', 'R'),
   ('B', '1-2-2019', 'Y'),
   ('B', '1-3-2019', 'Y'),
   ('B', '1-4-2019', 'R'),
   ('B', '1-5-2019', 'R'),
   ('B', '1-6-2019', 'R');

 set @seq := 0, @prev := 'B'

 SELECT 
     *, 
     IF(color='R', @seq := IF(@prev = color, @seq + 1, 1), NULL) AS rank,
     @prev := color as prev
 FROM tbl
 ORDER BY `group`, `date`

group | date     | color | rank | prev
:---- | :------- | :---- | ---: | :---
A     | 1-1-2019 | R     |    1 | R   
A     | 1-2-2019 | Y     |      | Y   
B     | 1-1-2019 | R     |    1 | R   
B     | 1-2-2019 | Y     |      | Y   
B     | 1-3-2019 | Y     |      | Y   
B     | 1-4-2019 | R     |    1 | R   
B     | 1-5-2019 | R     |    2 | R   
B     | 1-6-2019 | R     |    3 | R   

дБ <> скрипка здесь

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

Используйте оконную функцию row_number() для чисто стандартного решения SQL в Postgres - или любой современной СУБД, даже MySQL начиная с версии 8:

SELECT grp, the_date, color
     , row_number() OVER (PARTITION BY grp, color, part
                          ORDER BY the_date) AS rnk
FROM  (
   SELECT *
        , row_number() OVER (PARTITION BY grp ORDER BY the_date, color)
        - row_number() OVER (PARTITION BY grp, color ORDER BY the_date) AS part
   FROM   tbl
   ) sub
ORDER BY grp, the_date, color;

Это предполагает, что комбинация (grp, color, the_date) определена UNIQUE, дубликаты могут привести к недетерминированным результатам.

При вычитании двух разных номеров строк вычисляется различное число для каждого острова (part). Затем вы можете запустить row_number() еще раз, теперь разделение по подгруппам дополнительно. Вуаля.

Чтобы увидеть цифры только для определенного цвета, в примере 'R':

SELECT grp, the_date, color, CASE WHEN color = 'R' THEN rnk END AS rnk
FROM  (
   <<query from above, without ORDER BY>>
   ) sub
ORDER  BY grp, the_date, color;

В то время как основанное на множестве решение является преимуществом СУБД и, как правило, быстрее, процедурное решение требует только одного сканирования для этого типа проблемы, поэтому эта функция plpgsql должна быть существенно быстрее

CREATE OR REPLACE FUNCTION rank_color(_color text = 'R')  -- default 'R'
  RETURNS TABLE (grp text, the_date date, color text, rnk int) AS
$func$
DECLARE
   _last_grp text;
BEGIN
   FOR grp, the_date, color IN
      SELECT t.grp, t.the_date, t.color FROM tbl t ORDER BY 1,2
   LOOP
      IF color = $1 THEN
         IF _last_grp = grp THEN
            rnk := COALESCE(rnk + 1, 1);
         ELSE
            rnk := 1;
         END IF;
      ELSIF rnk > 0 THEN  -- minimize assignments
         rnk := NULL;
      END IF;

      RETURN NEXT;
      _last_grp := grp;
   END LOOP;
END
$func$ LANGUAGE plpgsql;

Звоните:

SELECT * FROM rank_color('R');

дБ <> скрипка здесь

Цикл не всегда неправильное решение в реляционной базе данных.

Дальнейшее чтение:

Кроме того: "rank" - это довольно вводящее в заблуждение название для этих номеров строк, если только у вас нет дубликатов, которые должны иметь одинаковый ранг ...

...