获取连续几天以来评论数最高的应用
Get apps with the highest review count since a dynamic series of days
我有两个table,apps
和reviews
(为方便讨论而简化):
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 ...
但我想知道是否有更好的方法来做到这一点。将数据拼接在一起似乎也是一个挑战。
如果您正在寻找提示,那么这里有一些提示:
- 您知道
generate_series()
以及如何使用它来组成 table 给定开始日期和结束日期的日期吗?如果没有,那么这个网站上有很多例子。
- 要回答任何给定日期的这个问题,您只需要为每个应用程序提供两个度量值,并且只有其中一个用于将一个应用程序与其他应用程序进行比较。您在第 1 部分中的查询表明您知道这两个度量是什么。
- 提示 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
得到一个结果行。或者,定义决胜局以最多获得 一个 获胜者。参见:
我有两个table,apps
和reviews
(为方便讨论而简化):
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 ...
但我想知道是否有更好的方法来做到这一点。将数据拼接在一起似乎也是一个挑战。
如果您正在寻找提示,那么这里有一些提示:
- 您知道
generate_series()
以及如何使用它来组成 table 给定开始日期和结束日期的日期吗?如果没有,那么这个网站上有很多例子。 - 要回答任何给定日期的这个问题,您只需要为每个应用程序提供两个度量值,并且只有其中一个用于将一个应用程序与其他应用程序进行比较。您在第 1 部分中的查询表明您知道这两个度量是什么。
- 提示 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
得到一个结果行。或者,定义决胜局以最多获得 一个 获胜者。参见: