2
votes

Comment identifier les lignes par groupe avant un certain écart de valeur?

Je voudrais mettre à jour une certaine colonne dans une table en fonction de la différence dans une autre valeur de colonne entre les lignes voisines dans PostgreSQL.

Voici une configuration de test:

FOR i in GROUP [PARTITION BY main ORDER BY sub_id]:
    DO until diff > 10 OR diff <-10
        SET newval = 1 AND LEAD(newval) = 1

Mon objectif est de déterminer dans chaque groupe main partir de sub_id 1 quelle valeur de diff dépasse un certain seuil (par exemple <10 ou> -10) en vérifiant par ordre croissant de sub_id . Jusqu'à ce que le seuil soit atteint, je voudrais marquer chaque ligne passée ET la ligne où la condition est FALSE en remplissant la colonne newval avec une valeur par exemple 1 .

entrez la description de l'image ici

Dois-je utiliser une boucle ou existe-t-il des solutions plus intelligentes?

La description de la tâche en pseudocode:

CREATE TABLE test(
   main INTEGER,
   sub_id INTEGER,
   value_t INTEGER);

INSERT INTO test (main, sub_id, value_t)
VALUES
    (1,1,8),
    (1,2,7),
    (1,3,3),
    (1,4,85),
    (1,5,40),
    (2,1,3),
    (2,2,1),
    (2,3,1),
    (2,4,8),
    (2,5,41);


3 commentaires

After locating this value I want to overwrite all following numbers << - utilisez EXISTS (...)


Votre logique d'exemples de code ne correspond pas à la description et au résultat attendu ci-dessus. Veuillez clarifier ceci.


OK fait. Veuillez jeter un œil à nouveau.


3 Réponses :


0
votes

EXISTS sur une sous-requête d'agrégation:


UPDATE 3
 main | sub_id | value_t 
------+--------+---------
    1 |      1 |       8
    1 |      2 |       7
    1 |      3 |       3
    1 |      4 |        
    1 |      5 |        
    2 |      1 |       3
    2 |      2 |       1
    2 |      3 |       1
    2 |      4 |       8
    2 |      5 |        
(10 rows)

Résultat:


UPDATE test u
SET value_t = NULL
WHERE EXISTS (
        SELECT * FROM (
                SELECT main,sub_id
                , value_t , ABS(value_t - lag(value_t)
                       OVER (PARTITION BY main ORDER BY sub_id) ) AS absdiff
                FROM test
                ) x
        WHERE x.main = u.main
        AND x.sub_id <= u.sub_id
        AND x.absdiff >= 10
        )
        ;

SELECT * FROM test
ORDER BY main, sub_id;


0 commentaires

1
votes

Votre question était difficile à comprendre, la colonne "value_t" n'était pas pertinente pour la question, et vous avez oublié de définir la colonne "diff" dans votre SQL.

Quoi qu'il en soit, voici votre solution:

WITH data AS (
  SELECT main, sub_id, value_t
       , abs(value_t
             - lead(value_t) OVER (PARTITION BY main ORDER BY sub_id)) > 10 is_evil
  FROM test
)
SELECT main, sub_id, value_t
     , CASE max(is_evil::int)
            OVER (PARTITION BY main ORDER BY sub_id
                  ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
       WHEN 1 THEN NULL ELSE 1 END newval
FROM data;

J'utilise un CTE pour préparer les données (calculant si une ligne est "mauvaise"), puis la fonction de fenêtre "max" est utilisée pour vérifier s'il y avait des lignes "mauvaises" avant celle actuelle, par partition.


2 commentaires

diff ne figure pas dans les données d'origine. abs(value_t - lead(value_t,1,0) OVER (ORDER BY main,sub_id))>10 AS is_evil


@clamp Merci pour la clarification - a mis à jour la requête en fonction de ce que vous avez écrit.



1
votes

SELECT base

Aussi vite que possible:

UPDATE test AS t
SET    newval = 1
FROM  (
   SELECT main, sub_id
        , bool_and(diff BETWEEN -10 AND 10) OVER (PARTITION BY main ORDER BY sub_id) AS flag
   FROM (
      SELECT main, sub_id
           , value_t - lag(value_t, 1, value_t) OVER (PARTITION BY main ORDER BY sub_id) AS diff
      FROM   test
      ) sub
   ) u
WHERE (t.main, t.sub_id) = (u.main, u.sub_id)
AND    u.flag;

Points fins

  • Votre modèle de pensée évolue autour de la fonction de fenêtre lead() . Mais son homologue lag() est un peu plus efficace pour cela, car il n'y a pas d'erreur de décalage lors de l'inclusion de la ligne avant le grand écart. Vous pouvez également utiliser lead() avec un ordre de tri inversé ( ORDER BY sub_id DESC ).

  • Pour éviter NULL pour la première ligne de la partition, fournissez value_t par défaut comme troisième paramètre, ce qui rend le diff 0 au lieu de NULL. lead() et lag() ont cette capacité.

  • diff BETWEEN -10 AND 10 est légèrement plus rapide que @diff < 11 (plus clair et plus flexible également). ( @ étant l'opérateur "valeur absolue" , équivalent à la fonction abs() .)

  • bool_or() ou bool_and() dans la fonction de fenêtre externe est probablement le moins cher pour marquer toutes les lignes jusqu'au grand espace.

Votre UPDATE

Jusqu'à ce que le seuil soit atteint, je voudrais marquer chaque ligne passée ET la ligne où la condition est FALSE en remplissant la colonne newval avec une valeur par exemple 1 .

Encore une fois, aussi vite que possible.

SELECT *, bool_and(diff BETWEEN -10 AND 10) OVER (PARTITION BY main ORDER BY sub_id) AS flag
FROM (
   SELECT *, value_t - lag(value_t, 1, value_t) OVER (PARTITION BY main ORDER BY sub_id) AS diff
   FROM   test
   ) sub;

Points fins

  • Le calcul de toutes les valeurs dans une seule requête est généralement beaucoup plus rapide qu'une sous-requête corrélée.

  • L'ajout de la condition WHERE AND u.flag garantit que nous ne mettons à jour que les lignes qui nécessitent réellement une mise à jour.
    Si certaines des lignes peuvent déjà avoir la bonne valeur dans newval , ajoutez une autre clause pour éviter ces mises à jour vides également: AND t.newval IS DISTINCT FROM 1 Voir:

  • SET newval = 1 assigne une constante (même si nous pourrions utiliser la valeur réellement calculée dans ce cas), c'est un peu moins cher.

db <> violon ici


0 commentaires