Bagaimana cara memilih set nilai NULL-NULL terakhir per kolom di atas grup?

9

Saya menggunakan SQL Server 2016 dan data yang saya konsumsi memiliki formulir berikut.

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;

masukkan deskripsi gambar di sini

Saya ingin mendapatkan nilai bukan nol terakhir di atas kolom val1dan val2dikelompokkan berdasarkan catdan dipesan oleh t. Hasil yang saya cari adalah

cat  val1 val2
A    1    P
B    10   C

Yang paling dekat yang saya gunakan adalah menggunakan LAST_VALUEsementara mengabaikan ORDER BYyang tidak akan berhasil karena saya membutuhkan nilai non-null terakhir yang dipesan.

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

Tabel aktual memiliki lebih banyak kolom untuk cat( kolom tanggal dan string) dan lebih banyak kolom val (kolom tanggal, string, dan angka) untuk memilih nilai bukan nol terakhir.

Ada ide bagaimana membuat pilihan ini.

Edmund
sumber
1
@ Vérace Dikelompokkan oleh catdipesan oleh t.
Edmund
1
@ ypercubeᵀᴹ Tidak, tidak ada nilai Q4 yang hilang, tnilainya berulang. Ini adalah data yang tidak berperilaku baik.
Edmund
4
Baiklah kecuali dalam hal itu, Anda harus memberikan pesanan yang menentukan pemesanan yang sempurna. PARTITION BY cat ORDER BY t, idsebagai contoh. Jika tidak, permintaan yang sama (permintaan apa pun) dapat memberi Anda hasil yang berbeda pada eksekusi terpisah. Jika kolom dalam tabel hanya yang Anda tampilkan, saya tidak melihat bagaimana kita dapat memiliki pesanan yang pasti!
ypercubeᵀᴹ
1
@ ypercubeᵀᴹ Di sinilah letak tantangannya. Tidak ada kolom id dalam data. Ada beberapa kolom pengelompokan, kolom string yang dapat digunakan untuk dalam pemesanan grup, dan kemudian beberapa kolom nilai dengan nulls diselingi.
Edmund
1
Jika Anda tidak dapat memberi tahu SQL Server secara pasti urutan urutannya, bagaimana konsumen data ini akan mengetahui perbedaannya?
Aaron Bertrand

Jawaban:

10

Menggunakan teknik gabungan dari The Last non NULL Puzzle oleh Itzik Ben Gan akan terlihat seperti ini dengan tabel data dan tipe data kolom Anda.

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;

masukkan deskripsi gambar di sini

Cara lain untuk menulis kueri ini yang membagi langkah-langkah menjadi CTE untuk mungkin lebih baik menunjukkan apa yang sedang terjadi. Ini memberikan rencana eksekusi yang sama persis seperti permintaan di atas.

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;

Solusi ini menggunakan fakta bahwa menggabungkan nilai nol dengan sesuatu menghasilkan nilai nol. SET CONCAT_NULL_YIELDS_NULL (Transact-SQL)

Mikael Eriksson
sumber
Mikael yang disuling dengan sangat baik. Solusi ini telah menyelamatkan saya beberapa kali, meskipun saya menemukan akhir dari artikel Itzik membingungkan pada awalnya. Dalam hal itu ia menamakannya "langkah 2" ketika dalam kenyataannya itu lebih seperti menerapkan logika di belakang langkah 1.
pimbrouwers
2

Cukup tambahkan tanda centang untuk NULL di partisi yang akan dilakukan

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
sumber
0

Ini harus dilakukan. row_number () dan gabung

Jika Anda tidak memiliki jenis yang baik, Anda harus berharap hanya satu dari Q3 yang tidak nol.

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
sumber