SQL:检测具有相同键的连续行的连续块

Posted

技术标签:

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

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

    一个 一个 B B B C C 一个 一个 C C

这里我想要的结果是

    A,1-2 乙,3-5 C,6-7 A, 8-9 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)

我怀疑这还不是答案。

【问题讨论】:

【参考方案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 才能在同一组中,并且是无间隙和连续的。你完全改变了问题;问一个新的问题比让答案无效要好。

以上是关于SQL:检测具有相同键的连续行的连续块的主要内容,如果未能解决你的问题,请参考以下文章

运行长度或连续相同值编码的 SQL 查询

SQL 查询 - 计算值大于 X 的连续行数

在 SQL 中检测和合并日期范围的连续重叠

如何根据条件选择R数据框中的连续行?

MySQL - 在匹配条件之后返回具有 n 个连续记录的所有记录

聚合 SQL 中的连续行