Используйте оконную функцию 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" - это довольно вводящее в заблуждение название для этих номеров строк, если только у вас нет дубликатов, которые должны иметь одинаковый ранг ...