如何对每个岛的连续记录进行编号?
How to number consecutive records per island?
我有一个 table 看起来像:
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
我试过使用 window 函数按组和颜色列进行分区,但它 returns 下面的输出是不正确的。
错误的查询和输出:
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)?
使用用户变量可以保持排名和以前的值来产生结果:
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
db<>fiddle here
这是使用 window 函数可以完成的方法。首先,我们创建一个 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
请注意,此查询还给出了 Y
值的排名,如果您希望这些值是 NULL
,请将 rank
的定义替换为:
CASE WHEN color = 'Y' THEN NULL
ELSE ROW_NUMBER() OVER (PARTITION BY seq_num)
END AS `rank`
在 Postgres 或任何现代 RDBMS 中使用 window function row_number()
作为纯标准 SQL 解决方案,甚至 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;
虽然基于集合的解决方案是 RDBMS 的强项,而且通常速度更快,但过程解决方案只需要对此类问题进行一次扫描,因此这个 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 = 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');
db<>fiddle here
循环并不总是关系数据库中的错误解决方案。
延伸阅读:
- Select longest continuous sequence
- GROUP BY and aggregate sequential numeric values
旁白:"rank" 对于这些行号来说是一个相当具有误导性的名称,除非您有重复项应该排名相同...
我有一个 table 看起来像:
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
我试过使用 window 函数按组和颜色列进行分区,但它 returns 下面的输出是不正确的。
错误的查询和输出:
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)?
使用用户变量可以保持排名和以前的值来产生结果:
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
db<>fiddle here
这是使用 window 函数可以完成的方法。首先,我们创建一个 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
请注意,此查询还给出了 Y
值的排名,如果您希望这些值是 NULL
,请将 rank
的定义替换为:
CASE WHEN color = 'Y' THEN NULL
ELSE ROW_NUMBER() OVER (PARTITION BY seq_num)
END AS `rank`
在 Postgres 或任何现代 RDBMS 中使用 window function row_number()
作为纯标准 SQL 解决方案,甚至 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;
虽然基于集合的解决方案是 RDBMS 的强项,而且通常速度更快,但过程解决方案只需要对此类问题进行一次扫描,因此这个 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 = 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');
db<>fiddle here
循环并不总是关系数据库中的错误解决方案。
延伸阅读:
- Select longest continuous sequence
- GROUP BY and aggregate sequential numeric values
旁白:"rank" 对于这些行号来说是一个相当具有误导性的名称,除非您有重复项应该排名相同...