如何以升序和降序排序方式多次自连接同一个表?

问题描述 投票:0回答:1

数据定义

我有一个像这样的状态表,省略了与问题无关的属性:

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
)折叠成一行,但是相应的 beforeafter 状态和时间戳也包括在内,如下所示:

错误首次出现 之前的值 时间戳_之前 之后的值 时间戳_之后
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 条记录的真实表上:

  • 生成结果表的首选 SQL 代码是什么?
  • 我需要创建哪些索引才能获得最佳速度。
postgresql sorting self-join
1个回答
0
投票

每个错误块需要来自三行的信息,因此最直接的方法是使用两个步骤,每个步骤将两个步骤组合起来。您可以使用自连接或窗口函数(例如

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
上的多列索引可能有助于加速初始自连接,但前提是您的错误和无错误块相当大,否则表的大多数行都会有无论如何都要扫描。

© www.soinside.com 2019 - 2024. All rights reserved.