2016-04-25 1 views
5

Comment puis-je demander à la fonction LAG d'obtenir la dernière valeur "not null"?Fonctions LAG et NULLS

Par exemple, voir ma table ci-dessous où j'ai quelques valeurs NULL sur les colonnes B et C. Je voudrais remplir les nulls avec la dernière valeur non nulle. J'ai essayé de le faire en utilisant la fonction LAG, comme ceci:

case when B is null then lag (B) over (order by idx) else B end as B, 

mais cela ne fonctionne pas tout à fait quand j'ai deux valeurs nulles ou plus dans une rangée (voir la valeur NULL sur la ligne colonne C 3 - I J'aimerais que ce soit 0.50 comme l'original).

Toute idée comment puis-je y parvenir? (il ne doit pas être en utilisant la fonction LAG, d'autres idées sont les bienvenues)

Quelques hypothèses:

  • Le nombre de lignes est dynamique;
  • La première valeur sera toujours non nulle;
  • Une fois que j'ai un NULL, est NULL tout jusqu'à la fin - donc je veux le remplir avec la dernière valeur.

Merci

enter image description here

+0

Itzik Ben-Gan a écrit un blog sur un problème: http://sqlmag.com/sql-server/how-previous-and-next-condition. Unfortunatley SQL Server ne supporte pas l'option 'IGNORE NULLS' dans' LAST_VALUE', alors c'est simple: 'LAST_VALUE (B IGNORE NULLS) OVER (ORDER BY idx)'. – dnoeth

Répondre

1

si elle est nulle tout le chemin jusqu'à la fin peut alors prendre un raccourci

declare @b varchar(20) = (select top 1 b from table where b is not null order by id desc); 
declare @c varchar(20) = (select top 1 c from table where c is not null order by id desc); 
select is, isnull(b,@b) as b, insull(c,@c) as c 
from table; 
+0

bonne approche, je ne voulais pas déclarer des variables, donc j'ai fini par faire quelque chose comme: cas où B est nul alors (sélectionnez top 1 B de où B n'est pas un ordre nul par idx desc) sinon B fin comme B super Idée, merci beaucoup – Diego

+0

Je pense que les variables sont plus propres à lire et cela vous assure que l'optimiseur de requête ne le fait qu'une seule fois. – Paparazzi

4

Vous pouvez faire un changement à votre ORDER BY, pour forcer le NULLs être le premier dans votre commande, mais peut-être cher ...

lag(B) over (order by CASE WHEN B IS NULL THEN -1 ELSE idx END) 

Ou, utilisez une sous-requête pour calculer la valeur de remplacement une fois. Peut-être moins cher sur les grands ensembles, mais très maladroit.
- repose sur tous les NULLs à venir à la fin
- Le LAG ne repose pas sur cette

COALESCE(
    B, 
    (
     SELECT 
      sorted_not_null.B 
     FROM 
     (
      SELECT 
       table.B, 
       ROW_NUMBER() OVER (ORDER BY table.idx DESC) AS row_id 
      FROM 
       table 
      WHERE 
       table.B IS NOT NULL 
     ) 
      sorted_not_null 
     WHERE 
      sorted_not_null.row_id = 1 
    ) 
) 

(Cela devrait être plus rapide sur les grands ensembles de données, que LAG ou à l'aide OUTER APPLY avec sous corrélative -queries, simplement parce que la valeur est calculée une fois par souci de cohérence, vous pouvez calculer et stocker le [last_known_value] pour chaque colonne dans les variables, puis il suffit d'utiliser COALESCE(A, @last_known_A), COALESCE(B, @last_known_B), etc)

+0

+1 qui semble fonctionner mais ma "table" est en fait une grosse requête que je ne veux pas vraiment lancer plus d'une fois, ce qui exigerait votre solution. Merci beaucoup pour l'aide – Diego

+0

@Diego - autre que l'utilisation de LAG, toute autre réponse ici (et chaque approche que je peux penser) va avoir ce problème. – MatBailie

6

vous pouvez le faire avec outer apply opérateur:.

select t.id, 
     t1.colA, 
     t2.colB, 
     t3.colC 
from table t 
outer apply(select top 1 colA from table where id <= t.id and colA is not null order by id desc) t1 
outer apply(select top 1 colB from table where id <= t.id and colB is not null order by id desc) t2 
outer apply(select top 1 colC from table where id <= t.id and colC is not null order by id desc) t3; 

Ceci fonctionnera, quel que soit le nombre de zéros ou d'îlots "nuls". Vous pouvez avoir des valeurs, puis des valeurs nulles, puis encore des valeurs, encore des valeurs nulles. Cela fonctionnera toujours.


Si toutefois l'hypothèse (dans votre question) détient:

Une fois que j'ai un NULL, est NULL tout jusqu'à la fin - alors je veux le remplir avec la dernière valeur.

il existe une solution plus efficace. Nous avons seulement besoin de trouver les dernières valeurs (lorsque commandées par idx).Modification de la requête ci-dessus, en supprimant le where id <= t.id des sous-requêtes:

select t.id, 
     colA = coalesce(t.colA, t1.colA), 
     colB = coalesce(t.colB, t2.colB), 
     colC = coalesce(t.colC, t3.colC) 
from table t 
outer apply (select top 1 colA from table 
      where colA is not null order by id desc) t1 
outer apply (select top 1 colB from table 
      where colB is not null order by id desc) t2 
outer apply (select top 1 colC from table 
      where colC is not null order by id desc) t3; 
+0

hey, merci, mais comme je l'ai dit, "Le nombre de lignes est dynamique" alors comment cela fonctionnerait-il avec 5 lignes? – Diego

+2

Cela fonctionnera simplement parfait. Quelle est votre préoccupation? –

+0

@diego - Cela fonctionnera quel que soit le nombre de lignes ... Mais il aurait un coût potentiellement exponentiel lorsque l'ensemble de données croît * (le coût de chaque sous-requête est plus élevé pour la 1000ème ligne que pour la 999ème ligne) *, mais il est certainement bien rangé pour les petits ensembles de données. – MatBailie

-3
UPDATE table 
SET B = (@n := COALESCE(B , @n)) 
WHERE B is null; 
+0

Cette question concerne 'SQL Server'. –

+0

Bonne idée, mais c'est la notation MySQL. Il peut * être * fait dans le serveur SQL, mais pas tout à fait écrit de cette façon. – MatBailie

+0

MISE À JOUR table SET B = (sélectionnez last_value (B ignorer les valeurs nulles) over (ordonnée par idx) b de la table) où B est nul; – Adesh