【问题标题】:SQL: detecting consecutive blocks of sequential rows with same keySQL:检测具有相同键的连续行的连续块
【发布时间】:2021-03-14 15:55:03
【问题描述】:

我的问题归结为以下几点。我有一个带有一些自然顺序的表,其中我有一个可能会随着时间重复的键值。我想找到键相同的块,然后更改,然后恢复原样。示例:

  1. 一个
  2. 一个
  3. B
  4. B
  5. B
  6. C
  7. C
  8. 一个
  9. 一个
  10. C
  11. C

这里我想要的结果是

  1. A,1-2
  2. 乙,3-5
  3. C,6-7
  4. A, 8-9
  5. C,10-11

所以我不能用那个键值A、B、C来分组,因为同一个键可以出现多次,我只是想挤出不间断的重复出现。

不用说,我想要最简单的 SQL。它将使用 OLAP 窗口函数。

我通常擅长处理复杂的 SQL,但处理序列我就不太擅长了。当然,我会自己做一些工作,并在后续编辑中在此问题下方附上一些想法。

让我们从定义表格开始讨论:

CREATE TABLE Seq (
  num integer,
  key char
);

更新 1:做一些研究,我在这里发现了一个类似的问题:How to find consecutive rows based on the value of a column?,但问题和答案都包含在很多额外的东西中并且令人困惑。

更新 2:我已经得到了一个答案,谢谢。现在检查它。这是我在说话时正在输入 PostgreSQL 的测试:

CREATE TABLE Seq ( num int, key char );
INSERT INTO Seq VALUES 
   (1, 'A'), (2, 'A'), 
   (2, 'B'), (3, 'B'), (5, 'B'), 
   (6, 'C'), (7, 'C'), 
   (8, 'A'), (9, 'A'), 
   (10, 'C'), (11, 'C');

更新 3:解决方案的第一个竞争者是这个

SELECT key, min(num), max(num)
  FROM (
    SELECT seq.*,
           row_number() over (partition by key order by num) as seqnum
      FROM Seq 
  ) s
  GROUP BY key, (num - seqnum) 
  ORDER BY min;

产量:

 key | min | max
-----+-----+-----
 A   |   1 |   2
 B   |   2 |   3
 B   |   5 |   5
 C   |   6 |   7
 A   |   8 |   9
 C   |  10 |  11
(6 rows)

由于某种原因,B 重复了两次,我明白为什么,我在测试数据中犯了一个“错误”,跳过序列号 4 并直接从 3 到 5。

这个错误是幸运的,因为它让我指出,虽然在这个例子中序列号是离散的,但我打算让序列来自某个连续域(例如时间)。

我犯了另一个“错误”,我重复了 num 2。这是允许的吗?可能不是。所以清理示例,删除重复但留下空白:

DROP TABLE Seq;
CREATE TABLE Seq ( num int, key char );
INSERT INTO Seq VALUES 
   (1, 'A'), (2, 'A'), 
   (3, 'B'), (4, 'B'), (6, 'B'), 
   (7, 'C'), (8, 'C'), 
   (9, 'A'), (10, 'A'), 
   (11, 'C'), (12, 'C');

这仍然给我们留下了重复的 B 块:

 key | min | max
-----+-----+-----
 A   |   1 |   2
 B   |   3 |   4
 B   |   6 |   6
 C   |   7 |   8
 A   |   9 |  10
 C   |  11 |  12
(6 rows)

现在按照 Gordon Linoff 的第一个直觉,尝试理解它并加以补充:

SELECT s.*, num - seqnum AS diff 
  FROM (
    SELECT seq.*,
           row_number() over (partition by key order by num) as seqnum
      FROM Seq
    ) s
  ORDER BY num;

这是分组前的 num - seqnum 技巧:

 num | key | seqnum | diff
-----+-----+--------+------
   1 | A   |      1 |    0
   2 | A   |      2 |    0
   3 | B   |      1 |    2
   4 | B   |      2 |    2
   6 | B   |      3 |    3
   7 | C   |      1 |    6
   8 | C   |      2 |    6
   9 | A   |      3 |    6
  10 | A   |      4 |    6
  11 | C   |      3 |    8
  12 | C   |      4 |    8
(11 rows)

我怀疑这还不是答案。

【问题讨论】:

    标签: sql sequence window-functions


    【解决方案1】:

    由于 Gordon 的解决方案建议,您不能直接使用 numRow_number 也是。

    select key, min(num), max(num)
    from (select seq.*,
                 row_number() over (order by  num) as rn, 
                 row_number() over (partition by key order by num) as seqnum
          from seq
         ) s
    group by key, (rn - seqnum)
    order by min(num);
    

    【讨论】:

      【解决方案2】:

      这回答了原来的问题。

      您可以枚举每个键的行并将其从num 中减去。瞧!当key 在相邻行上为常数时,此数字为常数:

      select key, min(num), max(num)
      from (select seq.*,
                   row_number() over (partition by key order by num) as seqnum
            from seq
           ) s
      group by key, (num - seqnum);
      

      Here 是一个 dbfiddle,表明它可以工作。

      【讨论】:

      • 不完全,我在上面显示你的查询结果。
      • @GuntherSchadow 。 . .您更改了数据——以及问题的定义。最初的问题暗示(非常强烈地)数字必须相差 1 才能在同一组中,并且是无间隙和连续的。你完全改变了问题;问一个新的问题比让答案无效要好。
      猜你喜欢
      • 2019-01-28
      • 1970-01-01
      • 2014-04-21
      • 1970-01-01
      • 1970-01-01
      • 2021-10-30
      • 2013-05-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多