The main reason for the slowness is (was) that you aggregate over the big table from scratch for every iteration of the lateral sibquery. Compute earliest review & current total count per app in CTE *once* and base the lateral subquery on it. I discussed that and some other optizations under your predating related question on SO: [Get apps with the highest review count since a dynamic series of days][1] One difference: In this question you also return an additional attribute of the app (`slug`), so we need to join to table `app` after all. Literally: join to `apps` *after* aggregating `reviews`, much cheaper, on top of the CTE: ```sql WITH cte AS ( -- MATERIALIZED SELECT a.id, a.slug, FROM apps a JOIN ( SELECT app_id, min(review_date) AS earliest_review, count(*)::int AS total_ct FROM reviews GROUP BY 1 ) r ON r.app_id = a.id 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` makes it a bit cheaper. Added in Postgres 13 (currently beta). See: - https://stackoverflow.com/questions/63178114/greater-than-or-equal-to-all-and-equal-to-max-speed/63184287#63184287 [1] https://stackoverflow.com/a/63320975/939860