背景
我有这样的情况,我将给定实体的所有版本存储在我的 PostgreSQL 数据库中。这是用两个表实现的;一张表存储实体的主键和不可变属性,另一张表存储实体的可变属性。两个表都是仅插入的(由触发器强制执行)。
例子
这个概念可以很容易地用实体 User
来说明,存储在 user
和 user_details
表中:
表用户
:
id timestamp
1 2018-04-10T12:00:00
2 2018-04-10T12:00:00
表user_details
:
id user_id username first_name last_name timestamp
1 1 bob Bob Socks 2018-04-10T12:00:01
2 1 bob Bobby Socks 2018-04-10T12:00:02
3 2 alice Alice Jones 2018-04-10T12:00:03
4 1 bob Bobbers Socks 2018-04-10T12:00:04
5 2 alice Alicia Jones 2018-04-10T12:00:05
“id”列都被定义为串行主键(严格递增),我在 user_details (user_id, id DESC)
上创建了一个索引。
1 - 如何有效查询实体的最新版本?
给定一个用户 ID,我需要一种快速的方法来获取 user
中的不可变数据和 user_details
中的最新条目。哪种查询最适合此联接?
2 - 如何有效查询实体的版本 n 和 n-1?
我通过首先获取 X 和 Y 之间带有 timestamp
的所有行来生成时间间隔的审计日志,然后我获取插入的行及其前身(相同的 user_id
,最接近的较低 id
)并从中产生差异。在 X 和 Y 之间插入的行数通常很高,因此我需要有效地获取当前 + 先前对,即给定输入 user_details(5)
,我需要选择user(2) + user_details(5)
和user(2) + user_details(3)
的连接。哪种查询最适合此联接?
徒劳的尝试
到目前为止,我最好的结果是这些查询:
问题1的查询:
SELECT *
FROM "user" u
JOIN LATERAL (SELECT *
FROM "user_details" ud
WHERE u.id = ud.user_id
ORDER BY id DESC
LIMIT 1
) detail ON TRUE
WHERE u.id IN
(...);
问题2的查询:
SELECT *
FROM "user" u
JOIN LATERAL (SELECT *
FROM "user_details" ud
WHERE u.id = ud.user_id
AND ud.id IN (...)
ORDER BY id DESC
LIMIT 2) ud ON TRUE;
但是,这两个查询最终都使用了嵌套循环(参见 EXPLAIN ANALYZE
),并且在使用大量 ID(5000+)运行时需要很长时间才能完成。
想法
我可以使用 user_details (user_id, id DESC)
索引以一种聪明的方式首先创建我需要的 user_details
ids 的 CTE 然后加入 user + user_details
基于这个?我可以创建某种功能索引吗?我是否需要在 user_details
(或另一个表)中维护一个 predecessor
列,以便能够有效地查找这种类型的关系?
谢谢!
SQL fiddle :http://www.sqlfiddle.com/#!17/5f5f0
解决方案
感谢 X 和 Y 将我推向正确的方向!我最终使用了@MichelMilezzi 为我的第一个问题建议的解决方案,并为我的第二个问题采用了@RadimBača 解决方案:
WITH
cte_1 AS (SELECT id, user_id FROM "user_details" WHERE id IN (8999, 9999)),
cte_2 as (SELECT cte_1.id, cte_1.user_id, prev.id AS prev_id, row_number() OVER (PARTITION BY cte_1.id, cte_1.user_id ORDER BY prev.id DESC) AS rownum FROM "user_details" prev, cte_1 WHERE prev.user_id = cte_1.user_id AND prev.id < cte_1.id)
SELECT main.*, detail.*, cte_2.id AS __id, (detail.id <> cte_2.id) AS __is_predecessor FROM "user" main, "user_details" detail, cte_2
WHERE main.id = cte_2.user_id AND cte_2.rownum = 1 AND (detail.id = cte_2.id OR detail.id = cte_2.prev_id);
最佳答案
您可以使用 DISTINCT ON
检索最新版本的用户,如下所示:
SELECT
DISTINCT ON (u.id)
*
FROM
"user" u
JOIN user_details d ON (u.id = d.user_id)
WHERE
d.id IN (100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
ORDER BY
u.id,
d.id DESC
引自docs :
SELECT DISTINCT ON ( expression [, ...] ) keeps only the first row of each set of rows where the given expressions evaluate to equal. The DISTINCT ON expressions are interpreted using the same rules as for ORDER BY (see above). Note that the “first row” of each set is unpredictable unless ORDER BY is used to ensure that the desired row appears first.
SQL fiddle here .
要获得旧版本,您可以使用@Radim 指出的窗口函数
。
关于sql - 如何在 PostgreSQL 中高效查询版本化行/实体?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49775834/