penso questo è quello che stai cercando:
Postgres 13 o successivo
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
lo rende un po' più economico. Aggiunto in Postgres 13 (attualmente beta). Vedi:
Postgres 12 o più vecchio
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<>violino qui
Come sopra, ma senza WITH TIES
.
Non è necessario coinvolgere la tabella apps
affatto. La tabella reviews
ha tutte le informazioni di cui abbiamo bisogno.
Il CTE cte
calcola la prima recensione e il conteggio totale corrente per app. Il CTE evita il calcolo ripetuto. Dovrebbe aiutare un bel po'.
È sempre materializzato prima di Postgres 12 e dovrebbe essere materializzato automaticamente in Postgres 12 poiché viene utilizzato molte volte nella query principale. Altrimenti potresti aggiungere la parola chiave MATERIALIZED
in Postgres 12 o successivo per forzarlo. Vedi:
Il generate_series()
ottimizzato call produce la serie di giorni dalla prima all'ultima revisione. Vedi:
- Generazione del tempo serie tra due date in PostgreSQL
- Unisciti a una query di conteggio su generate_series in postgres e recupera anche valori Null come "0"
Infine, il LEFT JOIN LATERAL
hai già scoperto. Ma poiché più app possono collegarsi per la maggior parte delle recensioni, recupera tutti i vincitori, che possono essere 0 - n app. La query aggrega tutti i vincitori giornalieri in un array, quindi otteniamo un'unica riga di risultati per review_window_start
. In alternativa, definisci i tiebreak per ottenerne al massimo uno vincitore. Vedi: