Conta tutti righe
SELECT date, '1_D' AS time_series, count(DISTINCT user_id) AS cnt
FROM uniques
GROUP BY 1
UNION ALL
SELECT DISTINCT ON (1)
date, '2_W', count(*) OVER (PARTITION BY week_beg ORDER BY date)
FROM uniques
UNION ALL
SELECT DISTINCT ON (1)
date, '3_M', count(*) OVER (PARTITION BY month_beg ORDER BY date)
FROM uniques
ORDER BY 1, time_series
-
Le tue colonne
week_beg
emonth_beg
sono ridondanti al 100% e possono essere facilmente sostituiti dadate_trunc('week', date + 1) - 1
edate_trunc('month', date)
rispettivamente. -
La tua settimana sembra iniziare di domenica (in meno di uno), quindi il
+ 1 .. - 1
. -
Il frame predefinito di una funzione finestra con
ORDER BY
nelOVER
la clausola utilizza èRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
. È esattamente ciò di cui hai bisogno. -
Usa
UNION ALL
, nonUNION
. -
La tua sfortunata scelta per
time_series
(D, W, M) non ordina bene, l'ho rinominato per rendere l'ultimoORDER BY
più facile. -
Questa query può gestire più righe al giorno. I conteggi includono tutti i peer per un giorno.
-
Ulteriori informazioni su
DISTINCT ON
:
Utenti DISTINTI al giorno
Per contare ogni utente solo una volta al giorno, utilizza un CTE con DISTINCT ON
:
WITH x AS (SELECT DISTINCT ON (1,2) date, user_id FROM uniques)
SELECT date, '1_D' AS time_series, count(user_id) AS cnt
FROM x
GROUP BY 1
UNION ALL
SELECT DISTINCT ON (1)
date, '2_W'
,count(*) OVER (PARTITION BY (date_trunc('week', date + 1)::date - 1)
ORDER BY date)
FROM x
UNION ALL
SELECT DISTINCT ON (1)
date, '3_M'
,count(*) OVER (PARTITION BY date_trunc('month', date) ORDER BY date)
FROM x
ORDER BY 1, 2
Distingue gli utenti in un periodo di tempo dinamico
Puoi sempre ricorrere a sottoquery correlate . Tendono ad essere lenti con i tavoli grandi!
Sulla base delle query precedenti:
WITH du AS (SELECT date, user_id FROM uniques GROUP BY 1,2)
,d AS (
SELECT date
,(date_trunc('week', date + 1)::date - 1) AS week_beg
,date_trunc('month', date)::date AS month_beg
FROM uniques
GROUP BY 1
)
SELECT date, '1_D' AS time_series, count(user_id) AS cnt
FROM du
GROUP BY 1
UNION ALL
SELECT date, '2_W', (SELECT count(DISTINCT user_id) FROM du
WHERE du.date BETWEEN d.week_beg AND d.date )
FROM d
GROUP BY date, week_beg
UNION ALL
SELECT date, '3_M', (SELECT count(DISTINCT user_id) FROM du
WHERE du.date BETWEEN d.month_beg AND d.date)
FROM d
GROUP BY date, month_beg
ORDER BY 1,2;
SQL Fiddle per tutte e tre le soluzioni.
Più veloce con dense_rank()
@Clodoaldo
ha apportato un notevole miglioramento:utilizzare la funzione finestra dense_rank()
. Ecco un'altra idea per una versione ottimizzata. Dovrebbe essere ancora più veloce escludere immediatamente i duplicati giornalieri. Il guadagno in termini di prestazioni aumenta con il numero di righe al giorno.
Basandosi su un modello di dati semplificato e sanificato - senza le colonne ridondanti- day
come nome della colonna invece di date
date
è una parola riservata in SQL standard
e un nome di tipo di base in PostgreSQL e non dovrebbe essere usato come identificatore.
CREATE TABLE uniques(
day date -- instead of "date"
,user_id int
);
Query migliorata:
WITH du AS (
SELECT DISTINCT ON (1, 2)
day, user_id
,date_trunc('week', day + 1)::date - 1 AS week_beg
,date_trunc('month', day)::date AS month_beg
FROM uniques
)
SELECT day, count(user_id) AS d, max(w) AS w, max(m) AS m
FROM (
SELECT user_id, day
,dense_rank() OVER(PARTITION BY week_beg ORDER BY user_id) AS w
,dense_rank() OVER(PARTITION BY month_beg ORDER BY user_id) AS m
FROM du
) s
GROUP BY day
ORDER BY day;
SQL Fiddle
dimostrando le prestazioni di 4 varianti più veloci. Dipende dalla distribuzione dei dati che è più veloce per te.
Tutti sono circa 10 volte più veloci della versione delle sottoquery correlate (il che non è male per le sottoquery correlate).