PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

PostgreSQL :nombre de lignes en cours pour une requête "par minute"

Renvoyer uniquement les 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.

Ne pas inclure id dans la requête, puisque vous voulez GROUP BY tranches minute.

count() est généralement utilisé comme fonction d'agrégat simple. Ajouter un OVER clause en fait une fonction de fenêtre. Omettre PARTITION BY dans la définition de la fenêtre - vous voulez un nombre cumulé sur toutes les lignes . Par défaut, cela compte de la première ligne au dernier pair de la ligne actuelle comme défini par ORDER BY . 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 sur toutes les lignes depuis le début de la partition jusqu'au dernier ORDER BY de la ligne actuelle pair.

Et c'est 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 , il n'a pas été spécifié dans la question, donc count(id) est faux , à proprement parler, car les valeurs NULL ne sont pas comptées avec count(id) .

Vous ne pouvez pas GROUP BY tranches d'une minute au même niveau de requête. Les fonctions d'agrégation sont appliquées avant fonctions de fenêtre, la fonction de fenêtre count(*) ne verrait qu'une ligne par minute de cette façon.
Vous pouvez cependant SELECT DISTINCT , car DISTINCT est appliqué après fonctions de fenêtre.

ORDER BY 1 est juste un raccourci pour ORDER BY date_trunc('minute', "when") ici.
1 est une référence de position à la 1ère expression dans le SELECT liste.

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;

Un peu comme ci-dessus, 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 externe .

Utilisez sum() en tant que fonction d'agrégation de fenêtre maintenant pour additionner les décomptes de la sous-requête.

J'ai trouvé cela beaucoup plus rapide avec de nombreuses lignes par minute.

Inclure les minutes sans activité

Le plus court

@GabiMe a demandé dans un commentaire comment obtenir une ligne pour chaque minute dans la période, y compris celles 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 le laps 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 au décompte. NULL les valeurs (où aucune ligne n'existe) ne s'ajoutent pas au nombre cumulé.

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égez et comptez les lignes par minute dans la première étape, cela omet le besoin 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;