获取连续几天以来评论数最高的应用

Get apps with the highest review count since a dynamic series of days

我有两个table,appsreviews(为方便讨论而简化):

apps table

id int

reviews table

id          int
review_date date
app_id      int (foreign key that points to apps)

2 个问题:

1。如何编写查询/函数来回答以下问题?:

给定一系列日期,从最早的 reviews.review_date 到最晚的 reviews.review_date(以一天递增),对于每个日期,D,哪些应用获得的评论最多,如果该应用的最早审核时间不晚于 D?

如果给出明确的日期,我想我知道如何编写查询:

SELECT
  apps.id,
  count(reviews.*)
FROM
  reviews
  INNER JOIN apps ON apps.id = reviews.app_id
group by
  1
having
  min(reviews.review_date) >= '2020-01-01'
  order by 2 desc
limit 10;

但我不知道如何在给定所需日期系列的情况下动态查询并在单个视图中编译所有这些信息。

2。对此数据建模的最佳方式是什么?

最好能有每个日期当时的评论数以及 app_id。截至目前,我在想可能看起来像的东西:

... 2020-01-01_app_id | 2020-01-01_review_count | 2020-01-02_app_id | 2020-01-02_review_count ...

但我想知道是否有更好的方法来做到这一点。将数据拼接在一起似乎也是一个挑战。

如果您正在寻找提示,那么这里有一些提示:

  1. 您知道 generate_series() 以及如何使用它来组成 table 给定开始日期和结束日期的日期吗?如果没有,那么这个网站上有很多例子。
  2. 要回答任何给定日期的这个问题,您只需要为每个应用程序提供两个度量值,并且只有其中一个用于将一个应用程序与其他应用程序进行比较。您在第 1 部分中的查询表明您知道这两个度量是什么。
  3. 提示 1 和 2 应该足以完成此操作。我唯一可以补充的是,您不必担心让数据库做“太多的工作”。这就是它要做的。如果它做的不够快,那么你可以考虑优化,但在你到达那一步之前,集中精力获得你想要的答案。

如果您需要进一步说明,请发表评论。

我缺少的部分是横向连接。 我可以使用以下方法完成我想要的:


select
  review_windows.review_window_start,
  id,
  review_total,
  earliest_review
from
  (
    select
      date_trunc('day', review_windows.review_windows) :: date as review_window_start
    from
      generate_series(
        (
          SELECT
            min(reviews.review_date)
          FROM
            reviews
        ),
        (
          SELECT
            max(reviews.review_date)
          FROM
            reviews
        ),
        '1 year'
      ) review_windows
    order by
      1 desc
  ) review_windows
  left join lateral (
    SELECT
      apps.id,
      count(reviews.*) as review_total,
      min(reviews.review_date) as earliest_review
    FROM
      reviews
      INNER JOIN apps ON apps.id = reviews.app_id
    where
      reviews.review_date >= review_windows.review_window_start
    group by
      1
    having
      min(reviews.review_date) >= review_windows.review_window_start
    order by
      2 desc,
      3 desc
    limit
      2
  ) apps_most_reviews on true;

认为这就是你要找的:

Postgres 13 或更新版本

WITH cte AS (  -- MATERIALIZED
   SELECT app_id, min(review_date) AS earliest_review, count(*)::int AS total_ct
   FROM   reviews
   GROUP  BY 1
   )
SELECT *
FROM  (
   SELECT generate_series(min(review_date)
                        , max(review_date)
                        , '1 day')::date
   FROM   reviews
   ) d(review_window_start)
LEFT  JOIN LATERAL (
   SELECT total_ct, array_agg(app_id) AS apps
   FROM  (
      SELECT app_id, total_ct
      FROM   cte c
      WHERE  c.earliest_review >= d.review_window_start
      ORDER  BY total_ct DESC
      FETCH  FIRST 1 ROWS WITH TIES  -- new & hot
      ) sub
   GROUP  BY 1
   ) a ON true;

WITH TIES 便宜一点。添加到 Postgres 13(目前是测试版)。参见:

Postgres 12 或更早版本

WITH cte AS (  -- MATERIALIZED
   SELECT app_id, min(review_date) AS earliest_review, count(*)::int AS total_ct
   FROM   reviews
   GROUP  BY 1
   )
SELECT *
FROM  (
   SELECT generate_series(min(review_date)
                        , max(review_date)
                        , '1 day')::date
   FROM   reviews
   ) d(review_window_start)
LEFT  JOIN LATERAL (
   SELECT total_ct, array_agg(app_id) AS apps
   FROM  (
      SELECT total_ct, app_id
          ,  rank() OVER (ORDER BY total_ct DESC) AS rnk
      FROM   cte c
      WHERE  c.earliest_review >= d.review_window_start
      ) sub
   WHERE  rnk = 1
   GROUP  BY 1
   ) a ON true;

db<>fiddle here

同上,但没有WITH TIES

我们根本不需要涉及 table apps。 table reviews 有我们需要的所有信息。

CTE cte 计算每个应用的最早评论和当前总数。 CTE 避免重复计算。应该很有帮助。
它总是在 Postgres 12 之前具体化,并且应该在 Postgres 12 中自动具体化,因为它在主查询中被多次使用。否则你可以在 Postgres 12 或更高版本中添加 keyword MATERIALIZED 来强制它。参见:

优化后的 generate_series() 调用会生成从最早到最新评论的一系列日期。参见:

  • Generating time series between two dates in PostgreSQL
  • Join a count query on generate_series() and retrieve Null values as '0'

最后,LEFT JOIN LATERAL 你已经发现了。但是由于 多个应用可以并列 最多的评论,检索所有获胜者,可以是 0 - n 个应用。该查询将所有每日获奖者聚合到一个数组中,因此我们每个 review_window_start 得到一个结果行。或者,定义决胜局以最多获得 一个 获胜者。参见: