Come selezionare l'insieme degli ultimi valori non NULL per colonna su un gruppo?


9

Sto usando SQL Server 2016 e i dati che sto consumando hanno il seguente modulo.

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;

inserisci qui la descrizione dell'immagine

Vorrei ottenere gli ultimi valori non nulli su colonne val1e val2raggruppati per cate ordinati per t. Il risultato che sto cercando è

cat  val1 val2
A    1    P
B    10   C

Il più vicino che ho trovato sta usando LAST_VALUEignorando ciò ORDER BYche non funzionerà poiché ho bisogno dell'ultimo valore non nullo ordinato.

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

La tabella effettiva ha più colonne per cat( colonne data e stringa) e più colonne val (colonne data, stringa e numero) per selezionare l'ultimo valore non nullo.

Qualche idea su come effettuare questa selezione.


1
@ Vérace Raggruppato per catordinato da t.
Edmund,

1
@ ypercubeᵀᴹ No, non vi è alcun valore Q4 mancante, i tvalori si ripetono. Non sono dati ben educati.
Edmund,

4
Va bene, ma in tal caso, devi fornire un ordine che determina un ordine perfetto. PARTITION BY cat ORDER BY t, idper esempio. Altrimenti, la stessa query (qualsiasi query) può fornire risultati diversi su esecuzioni separate. Se le colonne nella tabella sono solo quelle che mostri, non vedo come possiamo avere un determinato ordine!
ypercubeᵀᴹ

1
@ ypercubeᵀᴹ Qui sta la sfida. Non esiste una colonna ID nei dati. Esistono più colonne di raggruppamento, una colonna di stringa che può essere utilizzata per l'ordinamento di gruppo e quindi le colonne di più valori con valori null intervallati.
Edmund,

1
Se non riesci a dire in modo deterministico a SQL Server quale ordine dovrebbero essere le righe, come farà un consumatore di questi dati a conoscere la differenza?
Aaron Bertrand

Risposte:


10

L'utilizzo della tecnica di concatenazione di The Last non NULL Puzzle di Itzik Ben Gan sarebbe simile a questo con i tipi di dati di tabella e colonna di esempio.

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;

inserisci qui la descrizione dell'immagine

Un altro modo per scrivere questa query che divide i passaggi in CTE per mostrare forse meglio cosa sta succedendo. Fornisce esattamente lo stesso piano di esecuzione della query sopra.

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;

Questa soluzione utilizza il fatto che concatenare un valore null con qualcosa si traduce in un valore null. SET CONCAT_NULL_YIELDS_NULL (Transact-SQL)


Mikael molto ben distillato. Questa soluzione mi ha salvato diverse volte, anche se all'inizio ho trovato confuso l'articolo di Itzik. In quanto l'ha etichettato "passaggio 2" quando in realtà era più come implementare la logica dietro il passaggio 1.
pimbrouwers

2

Basta aggiungere un controllo per NULL nella partizione farà

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

0

Questo dovrebbe farlo. row_number () e un join

Se non hai un buon tipo devi sperare che solo uno dei Q3 non sia nullo.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.