Räkna alla rader
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
-
Dina kolumner
week_beg
ochmonth_beg
är 100 % redundanta och kan enkelt ersättas meddate_trunc('week', date + 1) - 1
ochdate_trunc('month', date)
respektive. -
Din vecka verkar börja på söndag (av ett), därför
+ 1 .. - 1
. -
standardramen för en fönsterfunktion med
ORDER BY
iOVER
satsanvändning ärRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
. Det är precis vad du behöver. -
Använd
UNION ALL
, inteUNION
. -
Ditt olyckliga val för
time_series
(D, W, M) sorterar inte bra, jag bytte namn för att göra den sistaORDER BY
lättare. -
Den här frågan kan hantera flera rader per dag. Räknar inkluderar alla kamrater för en dag.
-
Mer om
DISTINCT ON
:
DISTINKT användare per dag
För att bara räkna varje användare en gång per dag, använd 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
STYRKA användare över en dynamisk tidsperiod
Du kan alltid använda korrelerade underfrågor . Brukar vara långsam med stora bord!
Bygg på de tidigare frågorna:
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 för alla tre lösningarna.
Snabbare med dense_rank()
@Clodoaldo
kom med en stor förbättring:använd fönsterfunktionen dense_rank()
. Här är en annan idé för en optimerad version. Det borde gå ännu snabbare att utesluta dagliga dubbletter direkt. Prestandaökningen växer med antalet rader per dag.
Bygger på en förenklad och sanerad datamodell - utan de redundanta kolumnerna- day
som kolumnnamn istället för date
date
är ett reserverat ord i standard SQL
och ett grundläggande typnamn i PostgreSQL och bör inte användas som identifierare.
CREATE TABLE uniques(
day date -- instead of "date"
,user_id int
);
Förbättrad fråga:
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
demonstrerar prestandan hos 4 snabbare varianter. Det beror på din datadistribution vilken som är snabbast för dig.
Alla är ungefär 10 gånger så snabba som versionen av korrelerade underfrågor (vilket inte är dåligt för korrelerade underfrågor).