2016-04-25 9 views
5

Wie kann ich der LAG-Funktion mitteilen, den letzten Wert "nicht Null" zu erhalten?LAG-Funktionen und NULLEN

Zum Beispiel, siehe meine Tabelle unten, wo ich ein paar NULL-Werte in Spalte B und C habe. Ich möchte die Nullen mit dem letzten Nicht-Null-Wert füllen. Ich habe versucht, das zu tun, indem Sie die LAG-Funktion, etwa so:

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

aber das funktioniert nicht ganz, wenn ich zwei oder mehr Nullen in einer Reihe (den NULL-Wert in der Spalte C Zeile 3 sehen - I würde es als 0,50 wie das Original sein.

Eine Idee, wie kann ich das erreichen? (es hat nicht die LAG-Funktion zu verwenden, andere Ideen sind willkommen)

Einige Annahmen:

  • Die Anzahl der Zeilen ist dynamisch;
  • Der erste Wert wird immer ungleich null sein;
  • Sobald ich eine NULL habe, ist NULL alles bis zum Ende - also möchte ich es mit dem neuesten Wert füllen.

Dank

enter image description here

+0

Itzik Ben-Gan abkürzen schrieb Ein Blog über ein Problem: http://sqlmag.com/sql-server/how-previous-and-next-condition. Leider unterstützt SQL Server nicht die Option 'IGNORE NULLS' in' LAST_VALUE', dann ist es einfach: 'LAST_VALUE (B IGNORE NULLS) OVER (ORDER BY idx)'. – dnoeth

Antwort

1

wenn es null den ganzen Weg bis dann bis zum Ende kann

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

guten Ansatz hinzufügen, wollte ich nicht Variablen zu deklarieren, so endete ich etwas wie: Fall, wenn B dann null ist (wählen Sie oben 1 B von wo B ist nicht null Reihenfolge von idx desc) sonst B Ende als B tolle Idee, vielen Dank – Diego

+0

Ich denke Variable sind sauberer zu lesen und es versichert Ihnen, dass der Abfrage-Optimierer es nur einmal tut. – Paparazzi

4

Sie eine Änderung, um Ihre ORDER BY machen könnte, die NULL-Werte zu zwingen, in der Reihenfolge zuerst zu sein, aber das kann teuer werden ...

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

Oder verwenden Sie eine Unterabfrage, um den Ersatzwert einmal zu berechnen. Bei größeren Sets möglicherweise weniger teuer, aber sehr klobig.
- Stützt sich auf alle NULL-Werte am Ende kommenden
- Die LAG stützt sich nicht auf diesem

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

(Dies sollte schneller auf größere Datenmengen sein, als LAG oder mit OUTER APPLY mit korrelierten Unter -queries, einfach weil der Wert wird einmal berechnet für Reinlichkeit, könnten Sie berechnen und speichern Sie die [last_known_value] für jede Spalte in Variablen, dann COALESCE(A, @last_known_A), COALESCE(B, @last_known_B), etc nur verwenden)

+0

+1 scheint das zu funktionieren, aber meine "Tabelle" ist eigentlich eine große Abfrage, die ich nicht wirklich mehr als einmal ausführen möchte, was Ihre Lösung erfordern würde. Vielen Dank für die Hilfe – Diego

+0

@Diego - anders als mit LAG, jede andere Antwort hier (und jede Annäherung, die ich denken kann) wird dieses Problem haben. – MatBailie

6

Sie können es mit outer apply Betreiber.

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; 

Dies funktioniert, unabhängig von der Anzahl der Nullen oder null "Inseln". Sie können Werte haben, dann Nullwerte, dann wieder Werte, wieder Nullen. Es wird immer noch funktionieren.


Wenn jedoch die Annahme, (in Ihrer Frage) gilt:

Sobald ich ein NULL habe, ist NULL alle bis zum Ende - so möchte ich es mit dem letzten Wert füllen.

gibt es eine effizientere Lösung. Wir müssen nur die neuesten (wenn sie von idx bestellt werden) Werte finden.Ändern der obigen Abfrage, die where id <= t.id aus den Unterabfragen zu entfernen:

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, danke, aber wie gesagt, "Die Anzahl der Zeilen ist dynamisch" also wie würde das mit 5 Zeilen funktionieren? – Diego

+2

Es wird einfach perfekt funktionieren. Was ist deine Sorge? –

+0

@diego - Dies funktioniert unabhängig von der Anzahl der Zeilen ... Aber es würde möglicherweise exponentielle Kosten verursachen, wenn der Datensatz wächst * (die Kosten für jede Unterabfrage sind in der 1000. Zeile höher als in der 999. Zeile) *, aber für kleine Datensätze ist es sicher aufgeräumt. – MatBailie

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

Diese Frage bezieht sich auf 'SQL Server'. –

+0

Richtig, aber das ist die MySQL-Notation. Es * kann * im SQL Server gemacht werden, aber nicht ganz so geschrieben. – MatBailie

+0

UPDATE Tabelle SET B = (wähle letzten_Wert (B ignoriere Nullen) über (Reihenfolge nach idx) b aus Tabelle) wobei B ist Null; – Adesh