SQL 获取定长连续子序列

2020-12-14 10:04:00 浏览数 (1)

有一张表 savior,它的表结构及数据如下:

代码语言:javascript复制
CREATE TABLE `savior` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `status` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8
代码语言:javascript复制
    id  status  
------  --------
     1         1
     2         1
     3         0
     4         0
     5         0
     6         1
     7         0
     8         0
     9         0
    10         0
    11         0
    12         1
    13         1
    14         0
    15         0

savior 表有两个字段,id 是主键,设置了自动递增;status 表示状态,它只有 0/1 两种状态。

要求:从 savior 表中获取状态为 0 的 id,并且这些 id 能够组成长度为 3 的连续子序列。

比如,id = 3、4、5 的数据,它们的状态为 0,且它们构成的序列长度正好为 3。满足这个规则的还有(7,8,9)、(8,9,10)、(9,10,11)构成的序列。

最终期望能获取的结果:

代码语言:javascript复制
subseq      
--------
3~5     
7~9     
8~10    
9~11    

在历史的文章里有提到过判断连续子序列的方法,就是根据目标字段的排序规则生成序号,目标字段减去它对应的序号得到的的结果相同的数据则说明它们是连续的子序列。

请看演示:

代码语言:javascript复制
SELECT 
  id,
  rn,
  id - rn AS rs 
FROM
  (SELECT 
    id,
    row_number () over (
  ORDER BY id) AS rn 
  FROM
    savior 
  WHERE STATUS = 0) t ;
  

    id      rn      rs  
------  ------  --------
     3       1         2
     4       2         2
     5       3         2
     7       4         3
     8       5         3
     9       6         3
    10       7         3
    11       8         3
    14       9         5
    15      10         5

id 为 3 ~ 5 是一个连续子序列,7 ~ 11 是一个连续子序列,14 ~ 15 是一个连续子序列。

由于我们只要获取长度为 3 的子序列,根据判断连续子序列的规则,反过来说,如果一组数据是连续子序列,那么目标字段和它对应的序号分别加上固定的值,目标字段得到的结果和新序号的差值仍和做加法操作前保持一致。

比如,在 rs = 2 的子序列中,id = 3 和 rn = 1 分别加上 2,得到新的 id = 5 和 rn = 3,5 - 3 仍是 2 。

因此,可以将这个固定值作为定长子序列的长度参照(子序列的长度 = 固定值 1)。在这个需求里,这个固定值取值 2 。

代码语言:javascript复制
WITH cte AS 
(SELECT 
  *,
  row_number() over (
ORDER BY id) AS rn 
FROM
  savior 
WHERE STATUS = 0) 
SELECT 
  CONCAT_WS('~', a.id, b.id) AS subseq 
FROM
  cte a 
  INNER JOIN cte b 
    ON a.id   2 = b.id 
    AND a.rn   2 = b.rn 

0 人点赞