我正在尝试跟踪和显示用户每天在我的应用程序上连续发布的内容,但很难编写一个可靠运行并返回准确计数的查询。
我的应用程序有一个
prompt
和一个 post
表。用户可以为每个提示提交一篇帖子(提示每天都会创建,因此每个用户每天一篇帖子)。
简化后的
prompt
表如下所示:
id | 日期键 | 文字 |
---|---|---|
1 | 20240101 | 这是一个示例提示。 |
2 | 20240102 | 这是第二个提示。 |
简化后的
post
表类似于:
id | 内容 | 提示ID | 作者ID |
---|---|---|---|
50 | 这是我对提示的回应。 | 1 | 90 |
51 | 对同一提示的第二次响应。 | 1 | 91 |
我尝试了几种不同的查询方法(使用
PARTITION BY
、dense_rank()
等),但只能获得用户的最长连续记录。如果您有兴趣,我的疑问:
select distinct on (p."authorId") count(distinct "dateKey"::date) as "streak"
from (select p.*,
dense_rank() over (partition by p."authorId" order by "dateKey"::date) as seq
from post p
join prompt pt on p."promptId" = pt.id
) p
join prompt pt on p."promptId" = pt.id
where p."authorId" = 90
group by p."authorId", "dateKey"::date - seq * interval '1 day'
order by p."authorId", streak desc
这似乎适用于以下数据,但如果您添加新的“错过”提示(这应该重置条纹),此查询仍将返回 2 (我想我明白为什么,但不确定如何纠正它)。
我基本上需要一些东西来从最新的提示开始,然后沿着列表向下查找,直到找到没有来自该用户的帖子的提示。
例如,此连接数据将具有 2 的条纹:
id | 日期键 | 文字 | 帖子内容 | 作者ID |
---|---|---|---|---|
1 | 20240104 | 这是一个示例提示。 | 这是我的回应。 | 90 |
2 | 20240103 | 这是第二个提示。 | 第二个回应。 | 90 |
3 | 20240102 | 第三个提示。 | 空 | 空 |
4 | 20240101 | 我的第四个提示。 | 第三次回复,但我错过了一天。 | 90 |
功能最重要,但如果它也具有高性能那就太好了(
prompt
可能有 1000 行,post
可能有数百万行,streak
可能达到 1000)。
我对 PostgreSQL 的此类功能有点迷茫,所以希望有一个简单的解决方案!
小提琴:https://www.db-fiddle.com/f/4jyoMCicNSZpjMt4jFYoz5/11431
对于具有自然价值递进的单个表,有更简单的解决方案。但对于两个表与(看似)任意下一个
promptId
的组合,我希望 递归 CTE 表现最佳:
WITH RECURSIVE cte AS (
SELECT CURRENT_DATE AS the_day, p."authorId" AS author_id
FROM prompt pt
JOIN post p ON p."promptId" = pt.id
WHERE pt."dateKey" = CURRENT_DATE
AND p."authorId" = 90 -- your author here!
UNION ALL
SELECT c.the_day - 1, p."authorId" -- assuming no gaps in prompt!
FROM cte c
JOIN prompt pt ON pt."dateKey" = c.the_day - 1
JOIN post p ON p."promptId" = pt.id
WHERE p."authorId" = c.author_id
)
SELECT count(*)
FROM cte;
绝对需要索引支持才能快。
理想情况下,一个索引位于
prompt("dateKey", id)
,一个索引位于 post("authorId", "promptId")
。
假设...
prompt."dateKey"
是类型 date
(应该是这样)。相关:
如果每天最多有一个提示,请考虑使用日期(数据类型
date
!)作为表prompt
中的PK和表post
中的FK。允许更简单的查询。参见:
这看起来像是孤岛和间隙问题以及间隙问题。
我已经按照问题中的要求创建了单个
authorid
(p.authorId = 90
) 的查询。您可以删除连接条件来获取所有authorIds
的数据。
解决方案是使用窗口函数,如下所示:
select authorId, max(sm) from
(select t.*,
sum(case when prev_promptId is null then 1 end ) over (partition by p.authorId order by "dateKey"::date) as sm
from (select pt.*, p.*,
lag(p.promptId) over (partition by p.authorId order by "dateKey"::date) as prev_promptId
from prompt pt
left join post p on p.promptId = pt.id and p.authorId = 90) t ) t
group by authorId;