我正在使用 Postgres 14.x 顺便说一句。
我有一个可以观察的复杂查询,旁边是 EXPLAIN ANALYZE,这里。
history
表的目的是记录public
表中每一行的变化,以便我们随时了解过去的状态。该函数基本上为您提供了一个表,表示在提供的时间戳处 public
表的状态。
计算整个表过去的样子非常昂贵,但我的目的是通过使用该函数以及我想要的行 ID 的过滤器,然后只计算该行。但这并没有发生。
速度非常慢。应该需要几毫秒。但是,它不是在第一次扫描时执行过滤器
"id" = 'fd18211b-a400-49ed-a723-9648ab05ca4f'
来大大减少获取的行数,从而使聚合节点更快,而是在最后执行。
如果我将
COALESCE(past.id, present.id) AS id
替换为 past.id AS id
或 present.id AS id
,它就可以修复,如 here 所示,但它不正确,因为 past.id
或 present.id
可能是 NULL
。
但是如果
past.id
是 NULL
则意味着所有 past.*
都是 NULL。与present
相同。因此将过滤器应用于子查询仍然是有效的。
那么我怎样才能在保持 COALESCE 的同时通知规划器应该将过滤器应用于子查询呢?
除非 Postgres 可以在调用查询中“内联”函数(这对于返回集合的函数来说通常是不可能的),否则对于查询规划器来说它就是一个黑匣子。函数内部的所有内容都是与外部查询分开计划和执行的,并且在继续进行外部查询之前具体化结果。对你来说最坏的情况。 要尽早对 ID 应用最重要的过滤器,请将其传递给函数,然后将其应用到根。
另外,当您只需要最新的非空值时,不要
array-agg()
所有行。我展示了一个使用
UNION ALL
而不是 FULL OUTER JOIN
的解决方案,在子查询中订购一次,并与附加 first_last_agg模块中的函数
first()
进行聚合,您必须先安装该模块。此相关答案中的详细信息和替代方案:
CREATE OR REPLACE FUNCTION public.select_version_of_project(_id uuid, _ts timestamptz)
RETURNS TABLE (
id uuid
, folder_id uuid
, name text
--, etc.
)
LANGUAGE sql STABLE AS
$func$
SELECT p.id
, first(p.folder_id) FILTER (WHERE p.folder_id IS NOT NULL) AS folder_id
, first(p.name) FILTER (WHERE p.name IS NOT NULL) AS name
-- , etc.
FROM (
SELECT *
FROM history.project h
WHERE h.id = _id
AND h.updated_at <= _ts -- ?
UNION ALL
SELECT *
FROM public.project p
WHERE p.id = _id
AND p.updated_at <= _ts -- ?
ORDER BY updated_at DESC -- !! I assume you want the latest state
) p
GROUP BY 1;
$func$;
致电:
SELECT *
FROM public.select_version_of_project ('fd18211b-a400-49ed-a723-9648ab05ca4f'
, '2024-03-08 08:31:08.280+0') -- timestamptz !
我对
created_at
和
updated_at
的过滤器并不完全清楚。您可能需要在那里做更多事情。确保在 history.project(id, updated_at)
上有 多列索引!
不确定
public.project
(id)
上的索引就足够了,并且应该已经作为 PK 索引存在。传递声明的 timestamptz
值,否则您可能会得到令人惊讶的转换。