提问人:Adriano di Lauro 提问时间:10/31/2023 更新时间:10/31/2023 访问量:25
PostgreSQL - 按距离阈值压缩相邻行的性能
PostgreSQL - performance of squashing adjacent rows by a distance threshold
问:
背景
这是我问题的简化版本
我们得到了一个名为 的表,它存储了项目在多个容器中的移动。positions
每条记录包含
- 容器的名称(我们称之为
container
) - 两个 datetime 属性分别称为 和 ,它们包含项目进入和离开容器的时间戳
date_from
date_to
在两个连续的记录之间可能存在“时间间隔”。也就是说,物品在容器 A 内直到上午 10 点,然后在下午 4 点出现在容器 B 中,中间没有任何东西。
下面是一个示例数据集
编号 | container |
date_from |
date_to |
---|---|---|---|
1 | 一个 | 2023-10-01T00:00:00 | 2023-10-01T10:00:00 |
2 | 一个 | 2023-10-03T09:00:00 | 2023-10-03T11:00:00 |
3 | B | 2023-10-04T02:00:00 | 2023-10-04T03:00:00 |
4 | C | 2023-10-04T06:00:00 | 2023-10-04T08:00:00 |
5 | C | 2023-10-05T00:00:00 | 2023-10-06T10:00:00 |
6 | 一个 | 2023-10-06T11:00:00 | 2023-10-06T20:00:00 |
7 | C | 2023-10-06T21:00:00 | 2023-10-07T10:00:00 |
要求
我需要压扁所有连续的相邻位置
- 位于同一容器中(项目永远不会在子序列上离开该容器)
- 并且彼此“足够接近”:即对于第二个位置中的哪个位置在前一个位置的某个时间阈值内。
date_from
date_to
对于我压缩的每个子序列,我需要取 的第一个值和 的最后一个值 ,并将它们放在同一个结果行中。date_from
date_to
例如,如果容器 A 内有 5 条连续记录,并且根据规则,它们足够接近可以被压扁,那么我压扁这些位置的最后一行将具有
container
= 一个date_from
取自我压扁的 5 个位置中的第一个date_to
取自 5 个位置中的最后一个位置
我编写的 PostgreSQL 查询
WITH with_next_position AS (
SELECT
id,
container,
date_from,
date_to,
(
SELECT subquery.id
FROM positions subquery
WHERE subquery.date_from > base.date_from
ORDER BY subquery.date_from ASC
LIMIT 1
) AS next_position_id
FROM positions
),
with_time_lapse AS (
SELECT
with_next_position.date_from AS date_from,
with_next_position.date_to AS date_from,
with_next_position.container AS container,
CASE
WHEN join_table.date_from IS NOT NULL
THEN EXTRACT(EPOCH FROM (join_table.date_from - with_next_position.date_to))
ELSE
NULL
END AS time_lapse,
join_table.marina_id AS next_container
FROM
with_next_position
FULL OUTER JOIN with_next_position join_table ON join_table.id = with_next_position.next_position_id
WHERE
with_next_position.container IS NOT NULL
),
with_marked_to_squash AS (
SELECT
date_from,
date_to,
container,
CASE
WHEN next_container = container AND time_lapse <= 10000000 # This is where I put the threshold
THEN TRUE
ELSE
FALSE
END AS to_squash
FROM with_time_lapse
)
with_marked_first_to_squash AS (
SELECT
date_from,
date_to,
container,
CASE
WHEN to_squash
THEN (
SELECT CASE WHEN to_squash THEN FALSE ELSE TRUE END
FROM with_marked_to_squash subquery
WHERE subquery.date_from < with_marked_to_squash.date_from
ORDER BY subquery.date_from DESC
LIMIT 1
)
ELSE
FALSE
END AS first_to_squash
FROM with_marked_to_squash
),
with_first_to_squash AS (
SELECT
date_from,
date_to,
container,
(
SELECT subquery.date_from
FROM with_marked_first_to_squash subquery
WHERE subquery.date_from < with_marked_first_to_squash.date_from AND first_to_squash IS TRUE
ORDER BY subquery.date_from DESC
LIMIT 1
) AS first_date_in_position
FROM with_marked_first_to_squash
WHERE to_squash IS FALSE
)
SELECT
COALESCE(first_date_in_position, date_from) AS date_from,
date_to,
container
EXTRACT(EPOCH FROM (date_to - COALESCE(first_date_in_position, date_from))) AS time_spent
FROM with_first_to_squash
ORDER BY date_from
性能问题
上面的查询是正确的,它做了我期望它做的事情。但是,提取子查询时会出现性能问题。如果我将查询切到 BEFORE ,性能会呈指数级提高。with_first_to_squash
with_first_to_squash
我认为性能问题的原因是,通过连续运行和 ,我使数据库引擎通过两个嵌套循环:with_marked_first_to_squash
with_first_to_squash
- 首先,我们将那些已经标记为“to_squash”且是同类中的第一个位置标记为“first_to_squash”(即前一个位置未标记为“to_squash”):这是通过内联子查询完成的(在
with_marked_first_to_squash
) - 其次,我们只选择不会被压扁的位置(即每个相邻子序列中的最后一个位置),对于每个位置,我们运行一个子查询,该子查询“返回”,直到过去第一个被标记为“first_to_squash”的位置:一旦找到该位置,我们就用它来检索
date_from
在我删除第二个子查询的那一刻,事情变得飞快。
我确信有一个解决方案可以允许从子序列中的第一个位置提取,可能涉及分区,但我不熟悉分区及其语法。有没有人可以给我提示?date_from
答:
1赞
Mike Organek
10/31/2023
#1
我怀疑您列表中的子查询是扼杀您性能的原因。select
请尝试以下窗口函数解决方案来解决您的间隙和孤岛问题,因为它只需要排序一次:
with squashes as (
select *,
case
when container = lag(container) over w
and date_from - lag(date_to) over w <= interval '5 days' then false
else true
end as keep_me
from positions
window w as (order by date_from)
), islands as (
select *, sum(keep_me::int) over (order by date_from) as group_num
from squashes
)
select container, min(date_from) as date_from, max(date_to) as date_to
from islands
group by group_num, container
order by group_num;
工作小提琴
评论
0赞
The Impaler
10/31/2023
也许-->?window w as (order by date_from)
window w as (partition by container order by date_from)
0赞
Mike Organek
10/31/2023
@TheImpaler 我没有添加一个,因为提供的示例数据似乎是针对一个项目或 SKU 或其他什么。如果我按容器分区,那么这将取消按日期排序。partition by
1赞
Adriano di Lauro
10/31/2023
这很有效,谢谢!我不知道这是一个众所周知的 SQL 问题,我只是在谷歌上搜索了“gaps-and-islands”并找到了很多文章
评论