Tæl alle rækker
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
-
Dine kolonner
week_beg
ogmonth_beg
er 100 % redundante og kan nemt erstattes afdate_trunc('week', date + 1) - 1
ogdate_trunc('month', date)
hhv. -
Din uge ser ud til at starte på søndag (af én), derfor
+ 1 .. - 1
. -
standardrammen for en vinduesfunktion med
ORDER BY
iOVER
klausulens brug erRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
. Det er præcis, hvad du har brug for. -
Brug
UNION ALL
, ikkeUNION
. -
Dit uheldige valg til
time_series
(D, W, M) sorterer ikke godt, jeg omdøbte for at lave den endeligeORDER BY
lettere. -
Denne forespørgsel kan omhandle flere rækker pr. dag. Optællinger inkluderer alle jævnaldrende for en dag.
-
Mere om
DISTINCT ON
:
DISTINKTE brugere pr. dag
For kun at tælle hver bruger én gang om dagen, brug en CTE med 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
SÆRLIGE brugere over en dynamisk periode
Du kan altid ty til korrelerede underforespørgsler . Har tendens til at være langsom med store borde!
Bygger på de tidligere forespørgsler:
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 for alle tre løsninger.
Hurtigere med dense_rank()
@Clodoaldo
kom med en større forbedring:brug vinduefunktionen dense_rank()
. Her er endnu en idé til en optimeret version. Det burde være endnu hurtigere at udelukke daglige dubletter med det samme. Ydelsesgevinsten vokser med antallet af rækker pr. dag.
Bygger på en forenklet og renset datamodel - uden de redundante kolonner- day
som kolonnenavn i stedet for date
date
er et reserveret ord i standard SQL
og et grundlæggende typenavn i PostgreSQL og bør ikke bruges som identifikator.
CREATE TABLE uniques(
day date -- instead of "date"
,user_id int
);
Forbedret forespørgsel:
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
demonstrerer ydeevnen af 4 hurtigere varianter. Det afhænger af din datadistribution, hvilken der er hurtigst for dig.
De er alle omkring 10 gange så hurtige som versionen af korrelerede underforespørgsler (hvilket ikke er dårligt for korrelerede underforespørgsler).