SQLazy: 在分组内搜索指定偏移量的相邻记录

问题描述

某库表的 ProductionLine_Number 是分组字段,组内按 date_Time 排序。需要在每组中搜索 Cardboard_Number 等于指定字符串的所有记录,取出每条命中记录前后指定偏移量范围内的记录,合并并去除重复后输出。例如搜索 spL1ml82N4o、偏移量为 2 时,结果 ID 为 2,4,5,6,9,10,11,12。

源数据

id

Cardboard_Number

date_Time

ProductionLine_Number

2

WDL-005943998-1

2024-02-29 17:13:50

1

4

spL1ml82N4o

2024-02-29 17:13:54

1

5

WDL-005943998-1

2024-03-01 09:44:42

1

6

WDL-005943998-1

2024-03-01 10:34:57

1

7

950024027237

2024-03-01 10:44:57

1

8

950024027237

2024-03-01 10:52:57

1

9

WDL-005943998-1

2024-03-01 13:58:43

2

10

WDL-005943998-1

2024-03-01 13:58:46

2

11

spL1ml82N4o

2024-03-01 14:09:43

2

12

WDL-005943998-1

2024-03-12 15:48:36

2

期望结果

id

Cardboard_Number

date_Time

ProductionLine_Number

2

WDL-005943998-1

2024-02-29 17:13:50

1

4

spL1ml82N4o

2024-02-29 17:13:54

1

5

WDL-005943998-1

2024-03-01 09:44:42

1

6

WDL-005943998-1

2024-03-01 10:34:57

1

9

WDL-005943998-1

2024-03-01 13:58:43

2

10

WDL-005943998-1

2024-03-01 13:58:46

2

11

spL1ml82N4o

2024-03-01 14:09:43

2

12

WDL-005943998-1

2024-03-12 15:48:36

2

ProductionLine_Number=1 的组内按时间排序为 id 2,4,5,6,7,8,其中命中 spL1ml82N4o 的是 id=4。取 id=4 前后各 2 行,得到 id 2,4,5,6。

ProductionLine_Number=2 的组内按时间排序为 id 9,10,11,12,其中命中 spL1ml82N4o 的是 id=11。取 id=11 前后各 2 行,得到 id 9,10,11,12。

两组结果合并去重后为 2,4,5,6,9,10,11,12。注意 id=7、id=8 与命中行 id=4 的距离超过偏移量 2,因此不在结果中。

SQLazy 分步实现

核心思路:按分组字段和组内时间排序后,先用 compute 在每组内把命中目标值的行标记出来,再用相对位置区间语法 flag[-2:2] 取当前行前后各 2 行的窗口,判断窗口内是否包含命中行。若包含,则当前行属于结果范围。最后过滤并去重。

Name

Anchor

Statement

t1

table1

sort ProductionLine_Number, date_Time asc

t2

t1

compute (if (Cardboard_Number = "spL1ml82N4o" then 1)) , as flag; partition ProductionLine_Number

t3

t2

compute flag[-2:2] , max , as in_range; partition ProductionLine_Number

t4

t3

filter in_range = 1

t5

t4

distinct id

【点击在线运行本例】

下面逐一解释这些步骤。

第 1 步:按分组字段和组内时间排序

sort ProductionLine_Number, date_Time asc

将数据按 ProductionLine_Number 分组,组内按 date_Time 升序排列,保证后续的相对位置计算以正确的时间顺序进行。

Picture8png
第 2 步:标记命中目标值的行
compute (if (Cardboard_Number = “spL1ml82N4o” then 1)) , as flag; partition ProductionLine_Number
在每个 ProductionLine_Number 分组内,将 Cardboard_Number 等于目标字符串 spL1ml82N4o 的行标记为 1,其余行为空。partition 限定标记计算在各组内独立进行。

Picture9png
第 3 步:用区间语法判断窗口内是否命中(核心)
compute flag[-2:2] , max , as in_range; partition ProductionLine_Number
这是最关键的一步,使用了 SQLazy 的相对位置区间语法。flag[-2:2] 表示取当前行之前 2 行到之后 2 行这个窗口内的 flag 值,再用 max 聚合:只要窗口内存在命中行(flag=1),当前行的 in_range 就为 1。这样,每条命中行前后偏移 2 行范围内的所有记录都被覆盖到。

Picture10png
第 4 步:过滤出范围内的记录
filter in_range = 1
只保留 in_range 为 1 的行,即落在某条命中行前后偏移范围内的记录。

Picture11png
第 5 步:去除重复记录
distinct id
当多条命中行的偏移范围重叠时,同一行可能被多次选中,用 distinct id 去重,输出最终结果。

Picture12png

编译生成 SQL

确认上述步骤后,SQLazy 编译器自动生成原生 SQL(MySQL 语法):


WITH t2 AS (
    SELECT id, Cardboard_Number, date_Time, ProductionLine_Number
        , CASE
            WHEN Cardboard_Number = 'spL1ml82N4o' THEN 1
            ELSE NULL
        END AS flag
    FROM table1
),
t3 AS (
    SELECT id, Cardboard_Number, date_Time, ProductionLine_Number, flag
        , MAX(flag) OVER (PARTITION BY ProductionLine_Number ORDER BY CASE
            WHEN ProductionLine_Number IS NULL THEN 1
            ELSE 0
        END, ProductionLine_Number ASC, CASE
            WHEN date_Time IS NULL THEN 1
            ELSE 0
        END, date_Time ASC ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS in_range
    FROM t2
),
t4 AS (
    SELECT id, Cardboard_Number, date_Time, ProductionLine_Number, flag
        , in_range
    FROM t3
    WHERE in_range = 1
)
SELECT id, Cardboard_Number, date_Time, ProductionLine_Number, flag
    , in_range
FROM t4
GROUP BY id

SQLazy 让你用业务语言描述逻辑,而不是用 SQL 语法写嵌套查询。这个在分组内搜索指定偏移量相邻记录的例子,核心亮点是相对位置区间语法:flag[-2:2] 直接用下标区间表达前后各 2 行的窗口,对应 SQL 中冗长的 ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING。配合 compute 的 max 聚合,一句代码就完成了窗口中是否存在命中行的判断。partition 子句让所有计算在分组内独立进行,天然符合按组处理的需求;分步计算让排序、标记、区间判断、过滤、去重每一步都可独立验证中间结果,降低了复杂逻辑的出错概率。

官方链接

SQLazy 在线体验:sqlazy.com(免费,无需注册)
SQLazy 项目仓库:github.com/SPLWare/SQLazy