给定三个表profile
,topic
,message
,我想知道所有USER
配置文件是否删除了最后一个主题消息。
如果最后一条消息没有被删除,我想得到0.5
和0
,否则(例如,最后一条消息被删除,或者个人资料从未向该主题发送消息)。
我的查询具有正确的结果,但返回〜15.000个结果行大约需要25秒。
如何提高效率?理想情况是<1秒。
SELECT
p.id AS profile,
topic.id AS topic,
CASE WHEN m IS NULL THEN 0 ELSE 0.5 END AS value
FROM
profile p
CROSS JOIN topic
-- latest non deleted message per topic
LEFT JOIN message m ON (
m.profile_id = p.id
AND m.topic_id = topic.id
AND m.deleted = FALSE
AND NOT EXISTS (
SELECT m2 FROM message m2
WHERE m2.profile_id = p.id AND m.topic_id = m2.topic_id AND m.timestamp < m2.timestamp
)
)
WHERE
p.type = 'USER'
;
EXPLAIN
的结果
Hash Left Join (cost=395.85..1187910.62 rows=15204 width=48)
Hash Cond: ((p.id = m.profile_id) AND (topic.id = m.topic_id))
Join Filter: (NOT (SubPlan 1))
-> Nested Loop (cost=0.00..213.67 rows=15204 width=24)
-> Seq Scan on profile p (cost=0.00..22.36 rows=724 width=8)
Filter: ((type)::text = 'USER'::text)
-> Materialize (cost=0.00..1.31 rows=21 width=16)
-> Seq Scan on topic (cost=0.00..1.21 rows=21 width=16)
-> Hash (cost=223.15..223.15 rows=11513 width=89)
-> Seq Scan on message m (cost=0.00..223.15 rows=11513 width=89)
Filter: (NOT deleted)
SubPlan 1
-> Seq Scan on message m2 (cost=0.00..309.51 rows=1 width=0)
Filter: ((m."timestamp" < "timestamp") AND (profile_id = p.id) AND (m.topic_id = topic_id))
附注:我们需要经常执行查询,结果将被插入到另一个表(INSERT INTO ... SELECT (s. above)
)中以进行进一步处理。
解决方案
查看答案!
添加索引后,我将所有三个版本执行了混合10次。我正在其他计算机运行时在本地计算机上进行比较,因此它不是很科学-但结果似乎仍然很重要:
// results in ms
user | min | max | avg | portion of profiles that has type='USER'
Stuck | 171 | 216 | ~180 | ~96%
Gordon Linoff | 148 | 172 | ~160 | ~96%
sticky bit | 113 | 126 | ~120 | ~96% <-- winner
Gordon Linoff | 73 | 114 | ~90 | ~4% <-- winner when p.type='USER' is very selectiv
谢谢:)
答案 0 :(得分:2)
如果未删除最后一条消息,则我希望得到0.5和0(即,最后一条消息已删除,或者个人资料从未向该主题发送消息)。
我在想一些与stickybit类似的东西,但是措辞有些不同:
select p.id as profile, t.id as topic,
(case when not (select m.deleted
from messages m
where m.profile_id = p.id and
m.topic_id = t.id
order by m.timestamp desc
limit 1
)
then 0.5
else 0
end) as value
from profile p cross join
topic t
where p.type = 'user';
需要相同的索引:
messages(profile_id, topic_id, timestamp desc, deleted)
profile(type, id)
为什么这样说? distinct on
使用索引很快。但是,我怀疑简单的索引查找会更快。
第二,您没有指定type = 'user'
的选择性。此版本不处理其他配置文件上的消息,仅处理您关心的配置文件。
答案 1 :(得分:1)
嗯,也许尝试重写它,以便左联接使用一个子查询,该子查询仅包含每个主题和配置文件使用DISTINCT ON
删除的最后一条消息的状态。
SELECT p.id profile,
t.id topic,
CASE
WHEN coalesce(x.deleted,
true) THEN
0
ELSE
0.5
END value
FROM profile p
CROSS JOIN topic t
LEFT JOIN (SELECT DISTINCT ON (m.profile_id,
m.topic_id)
m.profile_id,
m.topic_id,
m.deleted
FROM message m
ORDER BY m.profile_id ASC,
m.topic_id ASC,
m.timestamp DESC) x
ON x.profile_id = p.id
AND x.topic_id = t.id
WHERE p.type = 'USER';
为此,以下指标应该很有希望。
CREATE INDEX message_pid_tid_ts_d
ON message (profile_id ASC,
topic_id ASC,
timestamp DESC,
deleted ASC);
CREATE INDEX profile_t_id
ON profile (type ASC,
id ASC);