Je dois interroger pour chaque minute le nombre total de lignes jusqu'à cette minute.
Le mieux que j'ai pu faire jusqu'à présent ne fait pas l'affaire. Il renvoie le décompte par minute, pas le décompte total jusqu'à chaque minute:
SELECT COUNT(id) AS count
, EXTRACT(hour from "when") AS hour
, EXTRACT(minute from "when") AS minute
FROM mytable
GROUP BY hour, minute
SELECT DISTINCT
date_trunc('minute', "when") AS minute
, count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM mytable
ORDER BY 1;
Utilisez date_trunc()
, il renvoie exactement ce dont vous avez besoin.
N'incluez pas id
dans la requête, car vous voulez GROUP BY
Minutes en tranches.
count()
est généralement utilisé comme simple fonction d'agrégation . L'ajout d'une clause OVER
en fait une fonction de fenêtre . Omettez PARTITION BY
Dans la définition de la fenêtre - vous voulez un compte courant sur tous lignes. Par défaut, cela compte de la première ligne au dernier homologue de la ligne actuelle, tel que défini par ORDER BY
. je cite le manuel :
L'option de cadrage par défaut est
RANGE UNBOUNDED PRECEDING
, Qui est identique àRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
. AvecORDER BY
, Cela définit le cadre pour que toutes les lignes de la partition démarrent jusqu'au dernier pairORDER BY
De la ligne actuelle.
Et cela se trouve être exactement ce dont vous avez besoin.
Utilisez count(*)
plutôt que count(id)
. Cela correspond mieux à votre question ("nombre de lignes"). Il est généralement légèrement plus rapide que count(id)
. Et, bien que nous puissions supposer que id
est NOT NULL
, Cela n'a pas été spécifié dans la question, donc count(id)
est incorrect, à proprement parler, car les valeurs NULL ne sont pas comptées avec count(id)
.
Vous ne pouvez pas GROUP BY
Tranche de minutes au même niveau de requête. Les fonctions d'agrégation sont appliquées avant les fonctions de fenêtre, la fonction de fenêtre count(*)
ne verrait que 1 ligne par minute de cette façon.
Vous pouvez cependant SELECT DISTINCT
, Car DISTINCT
est appliqué après les fonctions de la fenêtre.
ORDER BY 1
Est simplement un raccourci pour ORDER BY date_trunc('minute', "when")
ici.1
Est une référence de référence positionnelle à la 1ère expression de la liste SELECT
.
Utilisez to_char()
si vous devez formater le résultat. Comme:
SELECT DISTINCT
to_char(date_trunc('minute', "when"), 'DD.MM.YYYY HH24:MI') AS minute
, count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM mytable
ORDER BY date_trunc('minute', "when");
SELECT minute, sum(minute_ct) OVER (ORDER BY minute) AS running_ct
FROM (
SELECT date_trunc('minute', "when") AS minute
, count(*) AS minute_ct
FROM tbl
GROUP BY 1
) sub
ORDER BY 1;
Tout comme ce qui précède, mais:
J'utilise une sous-requête pour agréger et compter les lignes par minute. De cette façon, nous obtenons 1 ligne par minute sans DISTINCT
dans le SELECT
extérieur.
Utilisez sum()
comme fonction d'agrégation de fenêtres maintenant pour additionner les nombres de la sous-requête.
J'ai trouvé que c'était beaucoup plus rapide avec plusieurs lignes par minute.
@ GabiMe a demandé dans un commentaire comment obtenir une ligne eone pour tous les minute
dans la période, y compris ceux où aucun événement ne s'est produit (pas de ligne dans la table de base):
SELECT DISTINCT
minute, count(c.minute) OVER (ORDER BY minute) AS running_ct
FROM (
SELECT generate_series(date_trunc('minute', min("when"))
, max("when")
, interval '1 min')
FROM tbl
) m(minute)
LEFT JOIN (SELECT date_trunc('minute', "when") FROM tbl) c(minute) USING (minute)
ORDER BY 1;
Générez une ligne pour chaque minute dans l'intervalle de temps entre le premier et le dernier événement avec generate_series()
- ici directement basé sur les valeurs agrégées de la sous-requête.
LEFT JOIN
À tous les horodatages tronqués à la minute et compte. NULL
les valeurs (lorsqu'aucune ligne n'existe) ne s'ajoutent pas au décompte en cours.
Avec CTE:
WITH cte AS (
SELECT date_trunc('minute', "when") AS minute, count(*) AS minute_ct
FROM tbl
GROUP BY 1
)
SELECT m.minute
, COALESCE(sum(cte.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM (
SELECT generate_series(min(minute), max(minute), interval '1 min')
FROM cte
) m(minute)
LEFT JOIN cte USING (minute)
ORDER BY 1;
Encore une fois, agréger et compter les lignes par minute dans la première étape, il omet la nécessité de DISTINCT
plus tard.
Différent de count()
, sum()
peut renvoyer NULL
. Par défaut à 0
Avec COALESCE
.
Avec de nombreuses lignes et un index sur "when"
cette version avec une sous-requête était la plus rapide parmi quelques variantes que j'ai testées avec Postgres 9.1 - 9.4 :
SELECT m.minute
, COALESCE(sum(c.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM (
SELECT generate_series(date_trunc('minute', min("when"))
, max("when")
, interval '1 min')
FROM tbl
) m(minute)
LEFT JOIN (
SELECT date_trunc('minute', "when") AS minute
, count(*) AS minute_ct
FROM tbl
GROUP BY 1
) c USING (minute)
ORDER BY 1;