【问题标题】:How to number consecutive records per island?如何对每个岛屿的连续记录进行编号?
【发布时间】:2019-08-05 23:46:18
【问题描述】:

我有一张桌子,看起来像:

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)?

【问题讨论】:

  • 您使用的是 postgres (Microsoft SQL Server) 还是 mysql?你标记了 mysql 但你的 sql 看起来像 postgres
  • 我正在使用 presto,但任何数据库解决方案都值得赞赏。谢谢。
  • 您只想要R 的数字,而不是Y 或任何其他颜色?可以有 any 重复项吗?如果是,如何处理?
  • @RannieXue 。 . .我删除了不兼容的数据库标签。请仅使用您真正使用的数据库进行标记。

标签: sql gaps-and-islands


【解决方案1】:

使用用户变量可以保持排名和以前的值来产生结果:

 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小提琴here

【讨论】:

  • 附言。希望您使用 DATE 类型而不是像我在这里使用的 varchar。对于这个问题并不重要,但是以指定的方式开始订购/选择它,你会遇到麻烦。
  • 谢谢!这真的很有帮助!
【解决方案2】:

这就是如何使用窗口函数来完成的。首先,我们创建一个 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

Demo on dbfiddle

请注意,此查询还提供 Y 值的排名,如果您希望这些值为 NULL,请将 rank 的定义替换为:

CASE WHEN color = 'Y' THEN NULL
     ELSE ROW_NUMBER() OVER (PARTITION BY seq_num) 
     END AS `rank`

【讨论】:

  • 完美!谢谢!
【解决方案3】:

window function row_number() 用于Postgres 或任何现代RDBMS 中的纯标准SQL 解决方案,甚至是自版本8 以来的MySQL:

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 = $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');

db小提琴here

循环并不总是在关系数据库中是错误的解决方案。

进一步阅读:

除此之外:“排名”对于这些行号来说是一个颇具误导性的名称,除非您有应该排名相同的重复项......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多