如何在 PostgreSQL 中高效查询版本化 rows/entities?
How do I efficiently query versioned rows/entities in PostgreSQL?
背景
我遇到过将给定实体的所有版本存储在我的 PostgreSQL 数据库中的情况。这是用两个 table 实现的;一个 table 存储实体的主键和 immutable 属性,第二个 table 存储实体的 mutable 属性。两个 tables 都是只插入的(由触发器强制执行)。
例子
这个概念可以很容易地用实体 User
来说明,存储在 user
和 user_details
tables:
Table user
:
id timestamp
1 2018-04-10T12:00:00
2 2018-04-10T12:00:00
Table 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
中的 immutable 数据和 user_details
中的最新条目。哪种查询最适合此联接?
2 - 如何有效查询实体的版本 n 和 n-1?
我通过首先获取 X 和 Y 之间带有 timestamp
的所有行来生成时间间隔的审计日志,然后我获取插入的行及其前身(相同 user_id
,最近的较低 id
)并从中产生差异。在 X 和 Y 之间插入的行数通常很高,因此我需要有效地获取当前 + 先前对,即给定输入 user_details(5)
,我需要 select 连接 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
id 的 CTE,然后在此基础上加入 user + user_details
吗?我可以创建某种功能索引吗?我是否需要在 user_details
(或另一个 table)中维护一个 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);
考虑使用 window 函数
SELECT *
FROM "user" u
JOIN
(
SELECT row_number() over(partition by user_id order by id) rn,
*
FROM "user_details" ud
) t ON t.user_id = u.id
WHERE t.rn = 1
此解决方案还允许您查询每组的所有 N 行或每组 N-th 行。
您可以使用 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 指出的 window function
。
背景
我遇到过将给定实体的所有版本存储在我的 PostgreSQL 数据库中的情况。这是用两个 table 实现的;一个 table 存储实体的主键和 immutable 属性,第二个 table 存储实体的 mutable 属性。两个 tables 都是只插入的(由触发器强制执行)。
例子
这个概念可以很容易地用实体 User
来说明,存储在 user
和 user_details
tables:
Table user
:
id timestamp
1 2018-04-10T12:00:00
2 2018-04-10T12:00:00
Table 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
中的 immutable 数据和 user_details
中的最新条目。哪种查询最适合此联接?
2 - 如何有效查询实体的版本 n 和 n-1?
我通过首先获取 X 和 Y 之间带有 timestamp
的所有行来生成时间间隔的审计日志,然后我获取插入的行及其前身(相同 user_id
,最近的较低 id
)并从中产生差异。在 X 和 Y 之间插入的行数通常很高,因此我需要有效地获取当前 + 先前对,即给定输入 user_details(5)
,我需要 select 连接 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
id 的 CTE,然后在此基础上加入 user + user_details
吗?我可以创建某种功能索引吗?我是否需要在 user_details
(或另一个 table)中维护一个 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);
考虑使用 window 函数
SELECT *
FROM "user" u
JOIN
(
SELECT row_number() over(partition by user_id order by id) rn,
*
FROM "user_details" ud
) t ON t.user_id = u.id
WHERE t.rn = 1
此解决方案还允许您查询每组的所有 N 行或每组 N-th 行。
您可以使用 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 指出的 window function
。