Tel alles rijen
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
-
Uw kolommen
week_beg
enmonth_beg
zijn 100 % redundant en kunnen eenvoudig worden vervangen doordate_trunc('week', date + 1) - 1
endate_trunc('month', date)
respectievelijk. -
Je week lijkt op zondag te beginnen (één uit), daarom de
+ 1 .. - 1
. -
Gebruik
UNION ALL
, nietUNION
. -
Uw ongelukkige keuze voor
time_series
(D, W, M) sorteert niet goed, ik heb de naam hernoemd om de laatsteORDER BY
te maken makkelijker. -
Deze query kan meerdere rijen per dag behandelen. Tellingen zijn inclusief alle leeftijdsgenoten voor een dag.
-
Meer over
DISTINCT ON
:
DISTINCT gebruikers per dag
Om elke gebruiker slechts één keer per dag te tellen, gebruikt u een CTE met 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
DISTINCT gebruikers gedurende een dynamische periode
U kunt altijd uw toevlucht nemen tot gecorreleerde subquery's . De neiging om traag te zijn met grote tabellen!
Voortbouwend op de vorige vragen:
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 voor alle drie de oplossingen.
Sneller met dense_rank()
@Clodoaldo
kwam met een grote verbetering:gebruik de vensterfunctie dense_rank()
. Hier is nog een idee voor een geoptimaliseerde versie. Het zou nog sneller moeten zijn om dagelijkse duplicaten meteen uit te sluiten. De prestatiewinst groeit met het aantal rijen per dag.
Voortbouwen op een vereenvoudigd en opgeschoond gegevensmodel - zonder de overbodige kolommen- day
als kolomnaam in plaats van date
date
is een gereserveerd woord in standaard SQL
en een basistypenaam in PostgreSQL en mag niet als identifier worden gebruikt.
CREATE TABLE uniques(
day date -- instead of "date"
,user_id int
);
Verbeterde zoekopdracht:
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
demonstreert de prestaties van 4 snellere varianten. Het hangt af van uw gegevensdistributie welke voor u het snelst is.
Ze zijn allemaal ongeveer 10x zo snel als de gecorreleerde subquery-versie (wat niet slecht is voor gecorreleerde subquery's).