web-dev-qa-db-fra.com

PostgreSQL: exécution du nombre de lignes pour une requête 'par minute'

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
37
GabiMe

Retourne seulement quelques minutes avec activité

Le plus court

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. Avec ORDER BY, Cela définit le cadre pour que toutes les lignes de la partition démarrent jusqu'au dernier pair ORDER 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");

Le plus rapide

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.

Inclure des minutes sans activité

Le plus court

@ 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.

Le plus rapide

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;
92
Erwin Brandstetter