web-dev-qa-db-fra.com

SQL QUERY remplace la valeur NULL d'une ligne par une valeur de la valeur connue précédente

J'ai 2 colonnes

date   number       
----   ------
1      3           
2      NULL        
3      5           
4      NULL        
5      NULL        
6      2          
.......

J'ai besoin de remplacer les valeurs NULL par de nouvelles valeurs prend la valeur de la dernière valeur connue dans la date précédente dans la colonne de date, par exemple: date = 2 nombre = 3, date 4 et 5 nombre = 5 et 5. Les valeurs NULL apparaissent au hasard.

24
mike

Si vous utilisez Sql Server, cela devrait fonctionner

DECLARE @Table TABLE(
        ID INT,
        Val INT
)

INSERT INTO @Table (ID,Val) SELECT 1, 3
INSERT INTO @Table (ID,Val) SELECT 2, NULL
INSERT INTO @Table (ID,Val) SELECT 3, 5
INSERT INTO @Table (ID,Val) SELECT 4, NULL
INSERT INTO @Table (ID,Val) SELECT 5, NULL
INSERT INTO @Table (ID,Val) SELECT 6, 2


SELECT  *,
        ISNULL(Val, (SELECT TOP 1 Val FROM @Table WHERE ID < t.ID AND Val IS NOT NULL ORDER BY ID DESC))
FROM    @Table t
20
Adriaan Stander

Voici une solution MySQL:

UPDATE mytable
SET number = (@n := COALESCE(number, @n))
ORDER BY date;

Ceci est concis, mais ne fonctionnera pas nécessairement dans d'autres marques de SGBDR. Pour les autres marques, il peut exister une solution spécifique à la marque plus pertinente. C'est pourquoi il est important de nous indiquer la marque que vous utilisez.

Il est agréable d'être indépendant du fournisseur, comme l'a commenté @Pax, mais à défaut, il est également agréable d'utiliser la marque de base de données que vous avez choisie à son meilleur avantage.


Explication de la requête ci-dessus:

@n Est une variable utilisateur MySQL. Il commence par NULL et se voit attribuer une valeur sur chaque ligne lorsque la MISE À JOUR passe par les lignes. Lorsque number n'est pas NULL, @n Reçoit la valeur de number. Où number est NULL, la fonction COALESCE() prend par défaut la valeur précédente de @n. Dans les deux cas, cela devient la nouvelle valeur de la colonne number et la MISE À JOUR passe à la ligne suivante. La variable @n Conserve sa valeur de ligne en ligne, donc les lignes suivantes obtiennent des valeurs qui proviennent des lignes précédentes. L'ordre de la mise à jour est prévisible, en raison de l'utilisation spéciale de MySQL de ORDER BY avec UPDATE (ce n'est pas du SQL standard).

15
Bill Karwin

La meilleure solution est celle proposée par Bill Karwin. J'ai récemment dû résoudre ce problème dans un ensemble de résultats relativement volumineux (1000 lignes avec 12 colonnes nécessitant chacune ce type de "montrez-moi la dernière valeur non nulle si cette valeur est nulle sur la ligne actuelle") et en utilisant la méthode de mise à jour avec un top 1 sélectionner pour la valeur connue précédente (ou sous-requête avec un top 1) s'est déroulée très lentement.

J'utilise SQL 2005 et la syntaxe pour un remplacement de variable est légèrement différente de mysql:

UPDATE mytable 
SET 
    @n = COALESCE(number, @n),
    number = COALESCE(number, @n)
ORDER BY date

La première instruction set met à jour la valeur de la variable @n à la valeur actuelle de 'nombre' si le 'nombre' n'est pas nul (COALESCE renvoie le premier argument non nul que vous lui passez) La deuxième instruction set met à jour la valeur réelle valeur de colonne pour 'nombre' pour lui-même (sinon nul) ou la variable @n (qui contient toujours la dernière valeur non NULL rencontrée).

La beauté de cette approche est qu'il n'y a pas de ressources supplémentaires consacrées à l'analyse de la table temporaire encore et encore ... La mise à jour en ligne de @n prend en charge le suivi de la dernière valeur non nulle.

Je n'ai pas assez de représentants pour voter sa réponse, mais quelqu'un devrait le faire. C'est le plus élégant et le plus performant.

9
voutmaster

Voici la solution Oracle (10g ou plus). Il utilise la fonction analytique last_value() avec le ignore nulls option, qui remplace la dernière valeur non nulle de la colonne.

SQL> select *
  2  from mytable
  3  order by id
  4  /

        ID    SOMECOL
---------- ----------
         1          3
         2
         3          5
         4
         5
         6          2

6 rows selected.

SQL> select id
  2         , last_value(somecol ignore nulls) over (order by id) somecol
  3  from mytable
  4  /

        ID    SOMECOL
---------- ----------
         1          3
         2          3
         3          5
         4          5
         5          5
         6          2

6 rows selected.

SQL>
8
APC

Le script suivant résout ce problème et utilise uniquement du code ANSI SQL simple. J'ai testé cette solution sur SQL2008 , SQLite et Oracle11g .

CREATE TABLE test(mysequence INT, mynumber INT);

INSERT INTO test VALUES(1, 3);
INSERT INTO test VALUES(2, NULL);
INSERT INTO test VALUES(3, 5);
INSERT INTO test VALUES(4, NULL);
INSERT INTO test VALUES(5, NULL);
INSERT INTO test VALUES(6, 2);

SELECT t1.mysequence, t1.mynumber AS ORIGINAL
, (
    SELECT t2.mynumber
    FROM test t2
    WHERE t2.mysequence = (
        SELECT MAX(t3.mysequence)
        FROM test t3
        WHERE t3.mysequence <= t1.mysequence
        AND mynumber IS NOT NULL
       )
) AS CALCULATED
FROM test t1;
6
Gerardo Lima

Je sais que c'est un très vieux forum, mais je suis tombé sur ce problème lors du dépannage de mon problème :) je viens de réaliser que les autres gars ont donné une solution peu complexe au problème ci-dessus. Veuillez voir ma solution ci-dessous:

DECLARE @A TABLE(ID INT, Val INT)

INSERT INTO @A(ID,Val) SELECT 1, 3
INSERT INTO @A(ID,Val) SELECT 2, NULL
INSERT INTO @A(ID,Val) SELECT 3, 5
INSERT INTO @A(ID,Val) SELECT 4, NULL
INSERT INTO @A(ID,Val) SELECT 5, NULL
INSERT INTO @A(ID,Val) SELECT 6, 2

UPDATE D
    SET D.VAL = E.VAL
    FROM (SELECT A.ID C_ID, MAX(B.ID) P_ID
          FROM  @A AS A
           JOIN @A AS B ON A.ID > B.ID
          WHERE A.Val IS NULL
            AND B.Val IS NOT NULL
          GROUP BY A.ID) AS C
    JOIN @A AS D ON C.C_ID = D.ID
    JOIN @A AS E ON C.P_ID = E.ID

SELECT * FROM @A

J'espère que cela peut aider quelqu'un :)

4
Cyrus Christ

Si vous cherchez une solution pour Redshift, cela fonctionnera avec la clause frame:

SELECT date, 
       last_value(columnName ignore nulls) 
                   over (order by date
                         rows between unbounded preceding and current row) as columnName 
 from tbl
2
PieCharmed

Dans un sens très général:

UPDATE MyTable
SET MyNullValue = MyDate
WHERE MyNullValue IS NULL
2
Degan

Ceci est la solution pour MS Access.

L'exemple de table s'appelle tab, avec les champs id et val.

SELECT (SELECT last(val)
          FROM tab AS temp
          WHERE tab.id >= temp.id AND temp.val IS NOT NULL) AS val2, *
  FROM tab;
1
George

Tout d'abord, avez-vous vraiment besoin de stocker les valeurs? Vous pouvez simplement utiliser la vue qui fait le travail:

SELECT  t."date",
        x."number" AS "number"
FROM    @Table t
JOIN    @Table x
    ON  x."date" = (SELECT  TOP 1 z."date"
                    FROM    @Table z
                    WHERE   z."date" <= t."date"
                        AND z."number" IS NOT NULL
                    ORDER BY z."date" DESC)

Si vous avez vraiment la colonne ID ("date") et qu'il s'agit d'une clé primaire (clusterisée), cette requête devrait être assez rapide. Mais vérifiez le plan de requête: il pourrait être préférable d'avoir un index de couverture incluant également la colonne Val.

De plus, si vous n'aimez pas les procédures lorsque vous pouvez les éviter, vous pouvez également utiliser une requête similaire pour UPDATE:

UPDATE  t
SET     t."number" = x."number"
FROM    @Table t
JOIN    @Table x
    ON  x."date" = (SELECT  TOP 1 z."date"
                    FROM    @Table z
                    WHERE   z."date" < t."date" --//@note: < and not <= here, as = not required
                        AND z."number" IS NOT NULL
                    ORDER BY z."date" DESC)
WHERE   t."number" IS NULL

REMARQUE: le code doit fonctionner sur "SQL Server".

1
van
UPDATE TABLE
   SET number = (SELECT MAX(t.number)
                  FROM TABLE t
                 WHERE t.number IS NOT NULL
                   AND t.date < date)
 WHERE number IS NULL
0
OMG Ponies