如何计算用户的每日连胜数?

问题描述 投票:0回答:2

我的目标

我正在尝试跟踪和显示用户每天在我的应用程序上连续发布的内容,但很难编写一个可靠运行并返回准确计数的查询。

一些背景

我的应用程序有一个

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

sql postgresql query-optimization window-functions gaps-and-islands
2个回答
0
投票

对于具有自然价值递进的单个表,有更简单的解决方案。但对于两个表与(看似)任意下一个

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。允许更简单的查询。参见:


0
投票

这看起来像是孤岛和间隙问题以及间隙问题。

我已经按照问题中的要求创建了单个

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;
© www.soinside.com 2019 - 2024. All rights reserved.