2016-04-25 38 views
5

Come posso dire alla funzione LAG di ottenere l'ultimo valore "non null"?Funzioni LAG e NULLS

Ad esempio, vedere la mia tabella qui sotto dove ho alcuni valori NULL sulla colonna B e C. Mi piacerebbe riempire i valori null con l'ultimo valore non nullo. Ho cercato di farlo usando la funzione GAL, in questo modo:

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

ma che non funziona abbastanza quando ho due o più nulli in una fila (vedere il valore NULL sulla colonna C riga 3 - I 'mi piacerebbe essere 0.50 come l'originale).

Qualche idea su come ottenerlo? (non deve essere utilizzando la funzione GAL, altre idee sono benvenute)

Qualche ipotesi:

  • Il numero di righe è dinamico;
  • Il primo valore sarà sempre non nullo;
  • Una volta che ho un NULL, è NULL tutto fino alla fine, quindi voglio riempirlo con l'ultimo valore.

Grazie

enter image description here

+0

Itzik Ben-Gan ha scritto un blog su un problema: http://sqlmag.com/sql-server/how-previous-and-next-condition. Unfortunatley SQL Server non supporta l'opzione 'IGNORE NULLS' in' LAST_VALUE', quindi è semplice: 'LAST_VALUE (B IGNORE NULLS) OVER (ORDER BY idx)'. – dnoeth

risposta

1

se è nullo tutta la strada fino alla fine poi si può prendere una scorciatoia

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

good aproach, non volevo dichiarare variabili, quindi ho finito per fare qualcosa del tipo: caso quando B è null allora (seleziona top 1 B da dove B non è un ordine nullo di idx desc) altro B fine come B ottima idea, grazie mille – Diego

+0

Penso che le variabili siano più pulite da leggere e ti assicura che il Query Optimizer lo fa solo una volta. – Paparazzi

4

Si potrebbe fare una modifica al tuo ORDER BY, per forzare il NULL per essere i primi nel vostro ordine, ma che può essere costosa ...

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

Oppure, utilizzare una sottoquery per calcolare il valore di sostituzione una volta. Forse meno costoso su set più grandi, ma molto goffo.
- Si basa su tutti i NULL prossimi alla fine
- Il GAL non si basa su quella

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 
    ) 
) 

(Questo dovrebbe essere più veloce su insiemi di dati di grandi dimensioni, che LAG o utilizzando OUTER APPLY con sub correlato -queries, semplicemente perché il valore viene calcolato una volta per ordine, è possibile calcolare e memorizzare il [last_known_value] per ogni colonna variabili, poi basta usare COALESCE(A, @last_known_A), COALESCE(B, @last_known_B), etc)

+0

+1 sembra funzionare ma la mia "tabella" è in realtà una query di grandi dimensioni che non desidero eseguire più di una volta, che la soluzione richiederà. Grazie mille per l'aiuto – Diego

+0

@Diego - a parte l'utilizzo del LAG, ogni altra risposta qui (e ogni approccio a cui riesco a pensare) avrà questo problema. – MatBailie

6

si può fare con outer apply operatore:.

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; 

Questo funzionerà, indipendentemente dal numero di null o "isole" nulle. Potresti avere valori, quindi null, quindi ancora valori, ancora null. Funzionerà ancora.


Se però l'ipotesi (nella sua domanda) vale:

Una volta che ho un NULL, è NULL tutto fino alla fine - quindi voglio riempirlo con l'ultimo valore.

c'è una soluzione più efficiente. Abbiamo solo bisogno di trovare i valori più recenti (quando ordinati per idx).Modifica della query di cui sopra, la rimozione del where id <= t.id dalle sottointerrogazioni:

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, grazie, ma come ho detto, "Il numero di righe è dinamico", quindi come funzionerebbe con 5 righe? – Diego

+2

Funzionerà perfettamente. Qual è la tua preoccupazione? –

+0

@diego - Funzionerà indipendentemente dal numero di righe ... Ma avrebbe un costo potenzialmente esponenziale quando il set di dati cresce * (il costo di ciascuna query secondaria è più alto per la millesima fila rispetto alla 999 ° riga) *, ma è certamente ordinato per i piccoli set di dati. – MatBailie

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

Questa domanda riguarda 'SQL Server'. –

+0

Giusta idea, ma questa è la notazione MySQL. Può * essere * eseguito in SQL server, ma non scritto in questo modo. – MatBailie

+0

tabella UPDATE SET B = (selezionare last_value (B ignora null) su (ordine di idx) b dalla tabella) dove B è nullo; – Adesh