sql - 如何在 PostgreSQL 中高效查询版本化行/实体?

标签 sql postgresql greatest-n-per-group audit

背景

我有这样的情况,我将给定实体的所有版本存储在我的 PostgreSQL 数据库中。这是用两个表实现的;一张表存储实体的主键和不可变属性,另一张表存储实体的可变属性。两个表都是仅插入的(由触发器强制执行)。

例子

这个概念可以很容易地用实体 User 来说明,存储在 useruser_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?

我通过首先获取 XY 之间带有 timestamp 的所有行来生成时间间隔的审计日志,然后我获取插入的行及其前身(相同的 user_id,最接近的较低 id)并从中产生差异。在 XY 之间插入的行数通常很高,因此我需要有效地获取当前 + 先前对,即给定输入 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/

相关文章:

node.js - 有没有办法在 Sequelize.js 中包含包含在 JSONB 中的属性?

mysql - 如果列为空,则使用 Max() 列选择不会返回

sql - 两个unix时间戳之间的Postgresql查询

sql - 创建指向 HBase 表的外部 Hive 表

mysql - SQL 连接两个表,但如果第一个表存在于第二个表中,则覆盖第一个表

mysql - 查询 2 个表,其中一个字段链接到 2 个不同的值

postgresql - PostgreSQL 中的日期比较

sql - 将数据从一个表复制到同一数据库中的另一个表的有效方法

MYSQL 按列分组,每组 2 行

mysql - 尝试选择最大列学年