2016-04-25 19 views
5

Jak mogę powiedzieć funkcji LGD, aby uzyskać ostatnią wartość "nie zerową"?Funkcje LGD i NULLS

Na przykład zobacz tabelę poniżej, gdzie mam kilka wartości NULL w kolumnach B i C. Chciałbym wypełnić wartości zerowe ostatnią wartość inną niż null. Próbowałem to zrobić za pomocą funkcji LGD, tak:

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

ale to nie do końca pracy, gdy mam dwie lub więcej wartości null w rzędzie (patrz wartość NULL w kolumnie C rzędu 3 - I "Chciałbym, żeby było 0,50 jako oryginału).

Każdy pomysł, w jaki sposób mogę to osiągnąć? (nie muszą być za pomocą funkcji LGD, wszelkie inne pomysły są mile widziane)

Kilka założenia:

  • Liczba wierszy jest dynamiczna;
  • Pierwsza wartość zawsze będzie miała wartość inną niż null;
  • Kiedy już mam NULL, jest NULL aż do końca - więc chcę wypełnić go najnowszą wartością.

Dzięki

enter image description here

+0

Itzik Ben-Gan napisał blog dotyczący tego problemu: http://sqlmag.com/sql-server/how-previous-and-next-condition. Niefortunny SQL Server nie obsługuje opcji 'IGNORE NULLS' w' LAST_VALUE', wtedy jest to proste: 'LAST_VALUE (B IGNORE NULLS) OVER (ORDER BY idx)'. – dnoeth

Odpowiedz

1

jeśli jest null wszystko aż do końca to można wziąć skróty

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

jest dobra, nie chcę zadeklarować zmiennych, więc skończyłem na zrobieniu czegoś takiego: przypadku, gdy B ma wartość zerową (wybierz top 1 B z gdzie B nie jest pustym porządkiem według idx desc) w przeciwnym razie B kończy się jako B świetny pomysł, bardzo dziękuję – Diego

+0

Myślę, że zmienna jest czystsza w czytaniu i zapewnia, że ​​optymalizator zapytań robi to tylko raz. – Paparazzi

4

Można wprowadzić zmiany do swojego ORDER BY, aby wymusić nulls być pierwszym w kolejności, ale może być drogie ...

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

Lub użyj kwerendy podrzędnej, aby raz wyznaczyć wartość zastępczą. Prawdopodobnie mniej kosztowne na większych zestawach, ale bardzo przylegające.
- Opiera się na wszystkich wartości null najbliższych pod koniec
- LGD nie polega na tym

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

(Powinno być szybciej na większych zestawów danych, niż LAG lub używając OUTER APPLY z skorelowanej sub -queries, po prostu dlatego, że wartość jest obliczana raz za porządek, można obliczyć i przechowywać [last_known_value] dla każdej kolumny w zmiennych, a następnie po prostu użyć COALESCE(A, @last_known_A), COALESCE(B, @last_known_B), etc)

+0

+1, ale wydaje się, że działa, ale mój "stół" jest tak naprawdę dużym zapytaniem, którego nie chcę uruchamiać więcej niż raz, czego wymagałoby twoje rozwiązanie. Bardzo dziękuję za pomoc – Diego

+0

@Diego - inne niż używanie LGD, każda inna odpowiedź tutaj (i każde podejście, jakie mogę wymyślić) będzie miała ten problem. – MatBailie

6

można to zrobić z outer apply operatora.

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; 

To zadziała, niezależnie od liczby zer lub zerowych "wysp". Możesz mieć wartości, a następnie wartości null, a następnie ponownie wartości, ponownie wartości null. Nadal będzie działać.


Jeśli jednak założenie (w pytaniu) posiada:

Raz mam NULL jest NULL wszystko aż do końca - tak chcę, aby wypełnić go z najnowszym wartości.

jest bardziej wydajne rozwiązanie. Musimy tylko znaleźć najnowsze (gdy są zamówione przez idx) wartości.Modyfikacja powyższego zapytania, usuwając where id <= t.id z podzapytania:

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

hej, dzięki, ale jak już powiedziałem, "liczba wierszy jest dynamiczna", więc jak by to działało z 5 rzędami? – Diego

+2

Będzie działać idealnie. Jaki jest twój problem? –

+0

@diego - To zadziała niezależnie od liczby wierszy ... Jednak koszt zbioru potencjalnie wykładniczego wzrasta * (koszt każdego zapytania dodatkowego jest wyższy dla wiersza 1000 niż dla wiersza 999)) *, ale z pewnością jest uporządkowany dla małych zestawów danych. – MatBailie

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

To pytanie dotyczy "SQL Server". –

+0

Właściwy pomysł, ale to jest notacja MySQL. To * może * być wykonane na serwerze SQL, ale nie jest napisane w ten sposób. – MatBailie

+0

Tabela UPDATE SET B = (wybierz ostatnią wartość (B ignore nulls) over (według idx) b z tabeli), gdzie B jest puste; – Adesh