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

Nombre ordonné de répétitions / doublons consécutifs

Cas de test

Tout d'abord, une manière plus utile de présenter vos données - ou mieux encore, dans un sqlfiddle , prêt à jouer avec :

CREATE TEMP TABLE data(
   system_measured int
 , time_of_measurement int
 , measurement int
);

INSERT INTO data VALUES
 (1, 1, 5)
,(1, 2, 150)
,(1, 3, 5)
,(1, 4, 5)
,(2, 1, 5)
,(2, 2, 5)
,(2, 3, 5)
,(2, 4, 5)
,(2, 5, 150)
,(2, 6, 5)
,(2, 7, 5)
,(2, 8, 5);

Requête simplifiée

Comme cela reste flou, je suppose que ce qui précède est donné.
Ensuite, j'ai simplifié votre requête pour arriver à :

WITH x AS (
   SELECT *, CASE WHEN lag(measurement) OVER (PARTITION BY system_measured
                               ORDER BY time_of_measurement) = measurement
                  THEN 0 ELSE 1 END AS step
   FROM   data
   )
   , y AS (
   SELECT *, sum(step) OVER(PARTITION BY system_measured
                            ORDER BY time_of_measurement) AS grp
   FROM   x
   )
SELECT * ,row_number() OVER (PARTITION BY system_measured, grp
                             ORDER BY time_of_measurement) - 1 AS repeat_ct
FROM   y
ORDER  BY system_measured, time_of_measurement;

Maintenant, bien qu'il soit agréable et brillant d'utiliser du SQL pur, ce sera beaucoup plus rapide avec une fonction plpgsql, car elle peut le faire en une seule analyse de table où cette requête nécessite au moins trois analyses.

Plus rapide avec la fonction plpgsql :

CREATE OR REPLACE FUNCTION x.f_repeat_ct()
  RETURNS TABLE (
    system_measured int
  , time_of_measurement int
  , measurement int, repeat_ct int
  )  LANGUAGE plpgsql AS
$func$
DECLARE
   r    data;     -- table name serves as record type
   r0   data;
BEGIN

-- SET LOCAL work_mem = '1000 MB';  -- uncomment an adapt if needed, see below!

repeat_ct := 0;   -- init

FOR r IN
   SELECT * FROM data d ORDER BY d.system_measured, d.time_of_measurement
LOOP
   IF  r.system_measured = r0.system_measured
       AND r.measurement = r0.measurement THEN
      repeat_ct := repeat_ct + 1;   -- start new array
   ELSE
      repeat_ct := 0;               -- start new count
   END IF;

   RETURN QUERY SELECT r.*, repeat_ct;

   r0 := r;                         -- remember last row
END LOOP;

END
$func$;

Appel :

SELECT * FROM x.f_repeat_ct();

Assurez-vous de qualifier à tout moment les noms de colonne de vos colonnes dans ce type de fonction plpgsql, car nous utilisons les mêmes noms que les paramètres de sortie qui auraient priorité s'ils n'étaient pas qualifiés.

Des milliards de lignes

Si vous avez des milliards de lignes , vous pouvez fractionner cette opération. Je cite le manuel ici :

Remarque :L'implémentation actuelle de RETURN NEXT et RETURN QUERY stocke l'intégralité du jeu de résultats avant de revenir de la fonction, comme indiqué ci-dessus. Cela signifie que si une fonction PL/pgSQL produit un jeu de résultats très volumineux, les performances peuvent être médiocres :les données seront écrites sur le disque pour éviter l'épuisement de la mémoire, mais la fonction elle-même ne reviendra pas tant que le jeu de résultats complet n'aura pas été généré. Une future version de PL/pgSQL pourrait permettre aux utilisateurs de définir des fonctions renvoyant des ensembles qui n'ont pas cette limitation. Actuellement, le point auquel les données commencent à être écrites sur le disque est contrôlé par la variable work_memconfiguration. Les administrateurs qui disposent de suffisamment de mémoire pour stocker de plus grands ensembles de résultats en mémoire doivent envisager d'augmenter ce paramètre.

Envisagez de calculer des lignes pour un système à la fois ou définissez une valeur suffisamment élevée pour work_mem pour faire face à la charge. Suivez le lien fourni dans le devis pour en savoir plus sur work_mem.

Une façon serait de définir une valeur très élevée pour work_mem avec SET LOCAL dans votre fonction, qui n'est efficace que pour la transaction en cours. J'ai ajouté une ligne commentée dans la fonction. Ne pas réglez-le très haut globalement, car cela pourrait bombarder votre serveur. Lisez le manuel.