2012-08-03 17 views
5

tabeli jest jak tenZnajdź nazwę kolumny ostatniej wartości NULL w wierszu

 
ID A1 A2 A3 A4 A5 A6 A7 A8 A9 
1 YE YE YE NULL YE YE YE NULL NULL 
2 YE YE YE NULL NULL NULL NULL NULL NULL 
3 YE YE YE YE YE YE YE YE NULL 

gdzie id jest klucz podstawowy.
Chcę pobrać nazwę kolumny ostatniej wartości NULL w wierszu, wynik jest podobny do tego

 
ID LAST 
1 A7 
2 A3 
3 A8 

Każda pomoc w tej sprawie?

+0

Czy mogę powiedzieć "ten schemat wygląda okropnie"? :-) Można to zrobić z warunkowym priorytetem odwrotnym. –

Odpowiedz

2

Pomimo moich obaw dotyczących tego schematu, należy rozważyć tę "reverse-priorytet" Warunkowo:

select 
    id, 
    case 
    -- first match terminates search 
    when A9 is not null then 'A9' 
    when A8 is not null then 'A8' 
    when A7 is not null then 'A7' 
    .. 
    else null 
    as lastNonNullColumn 
from .. 

Kolejność oceny jest gwarantowana w TSQL (patrz CASE) więc po prostu inchworm tyłu :)

Oblicza w podanej kolejności wyrażenie Boolean_ dla każdej klauzuli WHEN.

również może UNPIVOT (lub ROLLUP [?] Lub instrukcja UNION) mogą być wykorzystane. Oznacza to, że obracają się stały zestaw nazw kolumn na wartości, to jest proste zapytanie .. To znaczy, jeśli stół był znormalizowany, można to zrobić łatwo :-)

select 
    id, 
    max(colName) as lastNonNullColumn 
from <<normalized_derived_table>> 
where colValue is not null 
group by id 
+1

Twoje wyrażenie CASE ma najlepszy plan wykonania i zajmuje najmniej procesora. Operacje UNPIVOT wymagają kosztownego SORT – ErikE

+0

Działa dobrze. Dziękuję wam wszystkim. – user1574813

2

Jak ten temat? Używa on UNPIVOT do transformacji danych, a następnie wybierzesz maksymalną, ostatnią wartość, która nie jest pusta/pusta.

;with cte as 
(
    select id 
    , last 
    , value 
    , row_number() over(partition by id order by last) rn 
    from 
    (
     select id, 
      isnull(a1, '') as a1, 
      isnull(a2, '') as a2, 
      isnull(a3, '') as a3, 
      isnull(a4, '') as a4, 
      isnull(a5, '') as a5, 
      isnull(a6, '') as a6, 
      isnull(a7, '') as a7, 
      isnull(a8, '') as a8, 
      isnull(a9, '') as a9 
     from t 
) x 
    unpivot 
    (
     value 
     for last in (a1, a2, a3, a4, a5, a6, a7, a8, a9) 
) u 
) 
select id, max(last) as last 
from cte 
where value != '' 
group by id 

Zobacz SQL Fiddle with Demo

Edycja, właściwie to nie musi być skomplikowane:

select id 
    , max(last) last 
from 
(
    select id, a1, a2, a3, a4, a5, a6, a7, a8, a9 
    from t 
) x 
unpivot 
(
    value 
    for last in (a1, a2, a3, a4, a5, a6, a7, a8, a9) 
) u 
group by id 

Zobacz SQL Fiddle with Demo

+0

Obie wersje działają, ale wymagają sortowania nazw kolumn (w tym braku A10 i wyższych). Jestem pewien, że kolumny PO nie są faktycznie nazwane A1 - A9. – ErikE

+0

@ErikE true, ale w oparciu o wymagania, które zostały wysłane, to zadziała. Gdyby rozszerzyli swoje potrzeby, byłoby to pomocne. OP stwierdza, że ​​tabela zawiera kolumny a1-a9. – Taryn

+0

Wystarczy! To dobra odpowiedź na dany problem. – ErikE

1

Oto wersja pseudo-UNPIVOT który pozwala określić kolejność kolumn (jeśli nazwy kolumn nie sortują według ich pozycji).

SELECT 
    T.ID, 
    X.Name 
FROM 
    T 
    CROSS APPLY (
     SELECT TOP 1 Name FROM (
     VALUES (1, 'A1', T.A1), (2, 'A2', T.A2), (3, 'A3', T.A3), (4, 'A4', T.A4), 
     (5, 'A5', T.A5), (6, 'A6', T.A6), (7, 'A7', T.A7), (8, 'A8', T.A8), 
     (9, 'A9', T.A9) 
    ) X (Pos, Name, Col) 
     WHERE Col IS NOT NULL 
     ORDER BY X.Pos DESC 
    ) X; 

Jednakże, podczas gdy rzeczywista IO i CPU nie są dużo gorsze niż naturalne metody UNPIVOT (plan wykonanie wygląda źle, ale rzeczywisty wpływ serwer nie jest dużo gorzej), to nie jest najlepszy wykonawca. Proste wyrażenie CASE podane przez @pst jest.

Zakładając nazwy kolumn może być sortowana jak to, że UNPIVOT można uprościć jeszcze bardziej:

SELECT ID, Max(Last) 
FROM T UNPIVOT (Value FOR Last IN (A1, A2, A3, A4, A5, A6, A7, A8, A9)) U 
GROUP BY ID; 

Wreszcie, oto szalona wersja myślałem że niestety wypada gorzej niż inni:

SELECT 
    T.ID, 
    Coalesce(
     (SELECT 'A9' WHERE T.A9 IS NOT NULL), 
     (SELECT 'A8' WHERE T.A8 IS NOT NULL), 
     (SELECT 'A7' WHERE T.A7 IS NOT NULL), 
     (SELECT 'A6' WHERE T.A6 IS NOT NULL), 
     (SELECT 'A5' WHERE T.A5 IS NOT NULL), 
     (SELECT 'A4' WHERE T.A4 IS NOT NULL), 
     (SELECT 'A3' WHERE T.A3 IS NOT NULL), 
     (SELECT 'A2' WHERE T.A2 IS NOT NULL), 
     (SELECT 'A1' WHERE T.A1 IS NOT NULL) 
    ) LastNotNullColumn 
FROM T 
ORDER BY ID 

Teoretycznie silnik mógłby wymyślić plan, który wygląda bardziej jak wersja ekspresyjna CASE, ale tak nie jest. Plan wygląda absolutnie szalenie, z jednym obiektem tabeli na instrukcję select i zajmuje około dwa razy CPU jako wyrażenie CASE.

Wszystkie wersje testowane używają tej samej liczby odczytów logicznych, różniących się tylko procesorem. Przetestowałem 15 000 wierszy.

Wreszcie, nie mogę z czystym sumieniem nie ostrzec, że twój schemat prawdopodobnie nie jest najlepszy. Chociaż nie mogę stwierdzić, jakie są twoje dane, to próbujesz znaleźć ostatnie, prawdopodobnie sugeruje, że kolumny reprezentują czas lub etapy pewnego cyklu życia - i to nie jest poprawny projekt bazy danych. Zamiast tego przechowuj dane niepodzielone. Kiedy nadejdzie czas, że potrzebujesz zestawu wyników, który jest obrócony, możesz PIVOT. I zapytanie o najnowszą wartość na ID staje się nieco prostsze!

Powiązane problemy