Wie wähle ich den Satz der letzten Nicht-NULL-Werte pro Spalte über einer Gruppe aus?

9

Ich verwende SQL Server 2016 und die Daten, die ich verwende, haben das folgende Formular.

CREATE TABLE #tab (cat CHAR(1), t CHAR(2), val1 INT, val2 CHAR(1));

INSERT INTO #tab VALUES 
    ('A','Q1',2,NULL),('A','Q2',NULL,'P'),('A','Q3',1,NULL),('A','Q3',NULL,NULL),
    ('B','Q1',5,NULL),('B','Q2',NULL,'P'),('B','Q3',NULL,'C'),('B','Q3',10,NULL);

SELECT *
FROM    #tab;

Geben Sie hier die Bildbeschreibung ein

Ich möchte die letzten Nicht-Null-Werte über Spalten erhalten val1und nach val2gruppiert catund sortiert nach t. Das Ergebnis, das ich suche, ist

cat  val1 val2
A    1    P
B    10   C

Das nächste, was ich verwendet habe, ist das LAST_VALUEIgnorieren des, ORDER BYwas nicht funktionieren wird, da ich den bestellten letzten Nicht-Null-Wert benötige.

SELECT DISTINCT 
        cat, 
        LAST_VALUE(val1) OVER(PARTITION BY cat ORDER BY (SELECT NULL) ) AS val1,
        LAST_VALUE(val2) OVER(PARTITION BY cat ORDER BY (SELECT NULL) ) AS val2
FROM    #tab
cat  val1 val2
A    NULL NULL
B    10   NULL

Die tatsächliche Tabelle enthält mehr Spalten für cat(Datums- und Zeichenfolgenspalten) und mehr Wertespalten (Datums-, Zeichenfolgen- und Zahlenspalten), um den letzten Wert ungleich Null auszuwählen.

Irgendwelche Ideen, wie man diese Auswahl trifft.

Edmund
quelle
1
@ Vérace Gruppiert nach catbestellt von t.
Edmund
1
@ ypercubeᵀᴹ Nein, es fehlt kein Q4-Wert, die tWerte wiederholen sich. Es handelt sich nicht um gut erzogene Daten.
Edmund
4
In Ordnung, aber in diesem Fall müssen Sie eine Bestellung aufgeben, die eine perfekte Bestellung bestimmt. PARTITION BY cat ORDER BY t, idzum Beispiel. Andernfalls kann dieselbe Abfrage (jede Abfrage) bei separaten Ausführungen zu unterschiedlichen Ergebnissen führen. Wenn die Spalten in der Tabelle nur die sind, die Sie anzeigen, sehe ich jedoch nicht, wie wir eine bestimmte Reihenfolge haben können!
Ypercubeᵀᴹ
1
@ ypercubeᵀᴹ Darin liegt die Herausforderung. Die Daten enthalten keine ID-Spalte. Es gibt mehrere Gruppierungsspalten, eine Zeichenfolgenspalte, die innerhalb der Gruppenreihenfolge verwendet werden kann, und dann die Spalten mit mehreren Werten, in die Nullen eingestreut sind.
Edmund
1
Wenn Sie SQL Server nicht genau bestimmen können, in welcher Reihenfolge die Zeilen angezeigt werden sollen, wie kann ein Verbraucher dieser Daten den Unterschied erkennen?
Aaron Bertrand

Antworten:

10

Die Verwendung der Verkettungstechnik aus The Last non NULL Puzzle von Itzik Ben Gan würde mit Ihren Datentabellen für Beispieltabellen und Spalten so aussehen.

select T.cat,
       cast(substring(
                     max(cast(T.t as binary(2)) + cast(T.val1 as binary(4))),
                     3,
                     4
                     ) as int),
       cast(substring(
                     max(cast(T.t as binary(2)) + cast(T.val2 as binary(1))),
                     3,
                     1
                     ) as char(1))
from #tab as T
group by T.cat;

Geben Sie hier die Bildbeschreibung ein

Eine andere Möglichkeit, diese Abfrage zu schreiben, die die Schritte in CTEs unterteilt, um möglicherweise besser zu zeigen, was vor sich geht. Es gibt genau den gleichen Ausführungsplan wie die obige Abfrage.

with C1 as
(
  -- Concatenate the ordering column with the value column
  select T.cat,
        cast(T.t as binary(2)) + cast(T.val1 as binary(4)) as val1,
        cast(T.t as binary(2)) + cast(T.val2 as binary(1)) as val2
  from #tab as T
),
C2 as
(
  -- Get the max concatenated value per group
  select C1.cat,
         max(C1.val1) as val1,
         max(C1.val2) as val2
  from C1
  group by C1.cat
)
-- Extract the value from the concatenated column
select C2.cat,
       cast(substring(C2.val1, 3, 4) as int) as val1,
       cast(substring(C2.val2, 3, 1) as char(1)) as val2
from C2;

Diese Lösung nutzt die Tatsache, dass das Verketten eines Nullwerts mit etwas zu einem Nullwert führt. SET CONCAT_NULL_YIELDS_NULL (Transact-SQL)

Mikael Eriksson
quelle
Sehr gut destilliertes Mikael. Diese Lösung hat mich einige Male gerettet, obwohl ich das Ende von Itziks Artikel zunächst verwirrend fand.
Insofern
2

Fügen Sie einfach eine Prüfung auf NULL in der Partition hinzu

SELECT DISTINCT 
        cat, 
        FIRST_VALUE(val1) OVER(PARTITION BY cat ORDER BY CASE WHEN val1 is NULL then 0 else 1 END DESC, t desc) AS val1,
        FIRST_VALUE(val2) OVER(PARTITION BY cat ORDER BY CASE WHEN val2 is NULL then 0 else 1 END DESC, t desc) AS val2
FROM    #tab
Kelvin
quelle
0

Das sollte es tun. row_number () und ein Join

Wenn Sie keine gute Sorte haben, müssen Sie hoffen, dass nur einer der Q3 nicht null ist.

declare @t TABLE (cat CHAR(1), t CHAR(2), val1 INT, val2 CHAR(1));
INSERT INTO @t VALUES 
    ('A','Q1',2,NULL),('A','Q2',NULL,'P'),('A','Q3',1,NULL),('A','Q3',NULL,NULL),
    ('B','Q1',5,NULL),('B','Q2',NULL,'P'),('B','Q3',NULL,'C'),('B','Q3',10,NULL);

--SELECT *
--     , row_number() over (partition by cat order by t) as rn
--FROM   @t
--where val1 is not null or val2 is not null;

select t1.cat, t1.val1, t2.val2 
from  ( SELECT t.cat, t.val1
             , row_number() over (partition by cat order by t desc) as rn
        FROM   @t t
        where val1 is not null 
       ) t1
join   ( SELECT t.cat, t.val2
             , row_number() over (partition by cat order by t desc) as rn
        FROM   @t t
        where val2 is not null 
       ) t2
   on t1.cat = t2.cat
  and t1.rn = 1
  and t2.rn = 1
Paparazzo
quelle