SQLazy:从事件表中查出下一组的开始时刻

问题描述

一张表记录了某个对象在不同时间点的状态值。每个时间点有一条记录,现在要按状态值是否发生变化来分段,输出每个状态的生效起始时间和结束时间——即把连续相同 value 的记录合并成一个时间段。

源数据

id

value

timestamp

1

A

2024-01-01 10:00:00

1

A

2024-01-02 08:00:00

1

A

2024-01-03 12:00:00

1

B

2024-01-04 09:00:00

1

B

2024-01-05 14:00:00

1

A

2024-01-06 11:00:00

1

A

2024-01-07 16:00:00

2

X

2024-02-01 08:00:00

2

X

2024-02-02 09:00:00

2

Y

2024-02-03 10:00:00

期望结果(每个值连续出现的区间):

id

value

effective_from

effective_to

1

A

2024-01-01 10:00:00

2024-01-04 09:00:00

1

B

2024-01-04 09:00:00

2024-01-06 11:00:00

1

A

2024-01-06 11:00:00

9999-12-31 00:00:00

2

X

2024-02-01 08:00:00

2024-02-03 10:00:00

2

Y

2024-02-03 10:00:00

9999-12-31 00:00:00

以对象 1 为例:

  • 前三条记录 value 都是 A,合并成一段:2024-01-01 ~ 2024-01-04(下一条变化的时间)

  • 第四、五条 value 是 B,合并成一段:2024-01-04 ~ 2024-01-06(下一条变化的时间)

  • 第六、七条 value 又变成 A,虽然值相同但中间被 B 隔断,所以作为新的一段:2024-01-06 ~ 无限远(最后一段结束时间为 9999-12-31)

SQLazy 分步实现

核心思路:按时间排序,判断当前行 value 是否发生变化。如果变化,则新开一组;否则归入同一组。最后取每组的最小时间作为起始时间,下一组的最小时间作为结束时间。

Value

Anchor

Statement

t1

events

sort timestamp

t2


segment value; change; as gid

t3


summarize timestamp first as effective_from, id first as id; group gid, value

t4


compute nvl(effective_from[1], datetime("9999-12-31 00:00:00")) as effective_to



derive delete gid

下面逐一解释这些步骤。

第 1 步:按时间升序排序

sort timestamp

将数据按 timestamp 升序排列,确保记录按时间先后处理。

..

第 2 步:值变化时新开一组

segment value; change; as gid

这是最核心的一步。segment 语句按 value 字段进行分段:每当 value 发生变化,就新开一个组,生成组号 gid。同一段内 value 保持不变。

..

第 3 步:按组号和值汇总,取起始时间

summarize timestamp first as effective_from, id first as id; group gid, value

按 gid 和 value 分组:

  • timestamp 取组内第一条记录的时间(first)作为该状态的生效起始时间

  • id 取组内第一个值

..

第 4 步:计算生效结束时间

*compute nvl(effective_from[1], datetime("9999-12-31 00:00:00")) as effective_to*

effective_from[1] 取当前组下一行的 effective_from 值,作为当前组的结束时间。如果当前组是最后一组(effective_from[1] 为空),则用 nvl 设置为极大日期(9999-12-31)表示 "至今"。

..

最后删除辅助列 gid,输出最终结果。

..

编译生成 SQL

确认上述 4 步逻辑后,SQLazy 编译器自动生成原生 SQL(这里是 Oracle 语法):

WITH Value AS (
    SELECT
        id,
        value,
        timestamp
    FROM
        events
),
Value2 AS (
    SELECT
        gid,
        id AS id,
        value AS value,
        timestamp AS effective_from
    FROM
        (
            SELECT
                id,
                value,
                timestamp,
                SUM(
                    CASE
                        WHEN value <> col__5 THEN 1
                        ELSE 0
                    END
                ) OVER (
                    ORDER BY
                        CASE
                            WHEN timestamp IS NULL THEN 1
                            ELSE 0
                        END,
                        timestamp ASC
                ) + 1 AS gid
            FROM
                (
                    SELECT
                        Value.*,
                        LAG(value) OVER (
                            ORDER BY
                                CASE
                                    WHEN timestamp IS NULL THEN 1
                                    ELSE 0
                                END,
                                timestamp ASC
                        ) AS col__5
                    FROM
                        Value
                ) sub__6
        ) Value1
    GROUP BY
        gid
)
SELECT
    gid,
    id,
    value,
    effective_from,
    LEAD(
        effective_from,
        1,
        TO_DATE('9999-12-31 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
    ) OVER (
        ORDER BY
            gid
    ) AS effective_to
FROM
    Value2
ORDER BY
gid;

你不需要手动调试这段 SQL,只需确认前面 4 步的逻辑正确,编译器就会输出可运行的代码。

SQLazy 让你用业务语言描述逻辑,而不是用 SQL 语法写嵌套查询。这个 "按值变化拆分时间段" 的例子,核心就是一句话:segment value; change,直接对应 "值变了就新开一组" 的业务需求。手写 SQL 时,你需要理解 LAG/LEAD 的窗口边界、手动处理 NULL、还要根据不同数据库调整语法差异。而 SQLazy 把这套复杂的逻辑压缩成 4 个直观步骤——排序、分段、汇总、取下一行作为结束时间——每一步都可以独立验证中间结果,编译器则帮你生成最终的可运行 SQL。你只需要确认每一步的业务含义是否正确,剩下的交给编译器。

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