我有一个像这样的状态表,省略了与问题无关的属性:
id | 已创建 | 价值 |
---|---|---|
1 | 2024-06-24T13:01:00 | 错误 |
2 | 2024-06-24T13:02:00 | 好的 |
3 | 2024-06-24T13:03:00 | 警告 |
4 | 2024-06-24T13:04:00 | 错误 |
5 | 2024-06-24T13:05:00 | 错误 |
6 | 2024-06-24T13:05:30 | 错误 |
7 | 2024-06-24T13:06:00 | 好的 |
8 | 2024-06-24T13:07:00 | 错误 |
9 | 2024-06-24T13:07:30 | 错误 |
10 | 2024-06-24T13:08:00 | 警告 |
11 | 2024-06-24T13:09:00 | 错误 |
我想将表格折叠成一个类似块的视图,其中“错误”blocs(
1
,4-6
,8-9
,11
)折叠成一行,但是相应的 before 和 after 状态和时间戳也包括在内,如下所示:
错误首次出现 | 之前的值 | 时间戳_之前 | 之后的值 | 时间戳_之后 |
---|---|---|---|---|
2024-06-24T13:01:00 | 空 | 空 | 好的 | 2024-06-24T13:02:00 |
2024-06-24T13:04:00 | 警告 | 2024-06-24T13:03:00 | 好的 | 2024-06-24T13:06:00 |
2024-06-24T13:07:00 | 好的 | 2024-06-24T13:06:00 | 警告 | 2024-06-24T13:08:00 |
2024-06-24T13:09:00 | 警告 | 2024-06-24T13:08:00 | 空 | 空 |
据我所知,我有以下选择:
1。创建子查询
SELECT
value AS "value_before"
-- created AS "timestamp_before"
FROM t AS t1
WHERE t1.value != 'error' AND t1.created < t.created
ORDER BY t1.created DESC
LIMIT 1
SELECT
value AS "value_after"
-- created AS "timestamp_after"
FROM t AS t2
WHERE t2.value != 'error' AND t2.created > t.created
ORDER BY t2.created ASC
LIMIT 1
2。横向连接
使用横向 JOIN 可能会节省一半的查询,因为我可以同时提取两个字段(
value
,created
),这对于子查询来说是不可能的。
但是,引擎可能需要对主查询生成的每一行再次执行排序。因此,我没有进一步追究这个问题。
3.自加入
在这里,我将创建两个派生表
t1
和 t2
(在本例中,CTE 是同一件事),并按 created
DESC
(最新的在前)和 ASC
(最旧的在前)排序并在 t1.value != 'error' AND t1.created < t.created
上加入“最新的不是错误且比 t.created 更旧的”,并在 t2.value != 'error' AND t2.created > t.created
上加入“最旧的不是错误且比 t.created 更旧的”。
SELECT
t.created "error_first_occurance",
t1.value "value_before",
t1.created "timestamp_before",
t2.value "value_after",
t2.created "timestamp_after"
FROM t LEFT
JOIN (
SELECT value, created
FROM t WHERE value != 'error'
ORDER BY created DESC
) t1 ON t1.created < t.created LEFT
JOIN (
SELECT value, created
FROM t WHERE value != 'error'
ORDER BY created ASC
) t2 ON t2.created > t.created
WHERE
t.value = 'error'
ORDER BY
t.created
这已经产生了正确的“超集”,但是由于我不能依赖
t1.value
或 t2.value
在块之间只有相同的值(参见第 2
-3
行),我不知道如何分辨我想要来自 MAX()
时间戳的 MIN()
/created
值,和 与该记录中的 value
相同。
挑战似乎是我不仅需要
created
时间戳字段(可以使用聚合函数轻松提取),而且还需要来自 相同记录的字符串
value
,聚合无法访问该字符串功能。
因此,在一个假定有 100k 条记录的真实表上:
每个错误块需要来自三行的信息,因此最直接的方法是使用两个步骤,每个步骤将两个步骤组合起来。您可以使用自连接或窗口函数(例如
lag()
)来完成此操作。剩下的就是把行排列正确。这是一个带有自连接的实现,这对我来说似乎更可取(更简洁):
WITH a AS (
SELECT
ROW_NUMBER() OVER (ORDER BY COALESCE(t1.id, -1)),
t1.value AS value_before,
t1.created AS timestamp_before,
t2.created AS error_first_occurrence,
t2.value,
t2.created
FROM
t t1
FULL JOIN t t2 ON t1.id+1 = t2.id
WHERE
(t1.value = 'error' AND t2.value IS DISTINCT FROM 'error')
OR (t1.value IS DISTINCT FROM 'error' AND t2.value = 'error')
)
SELECT
a1.error_first_occurrence,
a1.value_before,
a1.timestamp_before,
a2.value AS value_after,
a2.created AS timestamp_after
FROM
a a1
JOIN a a2 ON a1.row_number+1 = a2.row_number
WHERE
a1.value = 'error';
注意使用
IS DISTINCT
和 COALESCE
来处理 NULL
。
我认为这种方法和类似方法(例如使用窗口函数)在性能方面不会有太大差异,但如果它足够重要,您绝对应该尝试一些变体。
另一种方法是使用递归查询,但我认为在这种情况下这不会给你带来任何好处(除了更晦涩的语法)。然而,它们是处理此类问题(聚合连续行组)的最通用和最强大的工具,因此,如果您不断遇到类似的问题,您可能需要看看。
关于索引,我假设
id
是主键,所以最重要的索引已经存在了。除此之外,id
和value
上的多列索引可能有助于加速初始自连接,但前提是您的错误和无错误块相当大,否则表的大多数行都会有无论如何都要扫描。