我的情况是我在PostgreSQL数据库中存储给定实体的所有版本。这是用两个表实现的;一个表存储实体的主键和不可变属性,另一个表存储实体的可变属性。两个表都是仅插入的(由触发器强制执行)。
可以使用存储在User
和user
表中的实体user_details
轻松说明该概念:
表user
:
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)
上创建了一个索引。
鉴于用户ID,我需要一种快速方法来获取user
中的不可变数据以及user_details
中的最新条目。哪种查询最适合此加入?
我通过首先在 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
ID的CTE,然后根据此加入user + user_details
?我可以创建某种功能索引吗?我是否需要在predecessor
(或其他表格)中维护user_details
列才能有效地查找此类型的关系?
谢谢!
SQL小提琴: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);
答案 0 :(得分:1)
您可以使用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(表达式[,...])仅保留第一行 给定表达式求值等于的每组行。该 DISTINCT ON表达式使用与之相同的规则进行解释 ORDER BY(见上文)。请注意,每组的“第一行”是 不可预测,除非使用ORDER BY来确保所需的行 首先出现。
Sql fiddle here。
要获得旧版本,您可以使用@Radim指向的window function
。
答案 1 :(得分:0)
考虑使用窗口函数
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行。