Làm cách nào để chọn tập hợp các giá trị không phải NULL cuối cùng cho mỗi cột trong một nhóm?


9

Tôi đang sử dụng SQL Server 2016 và dữ liệu tôi đang tiêu thụ có dạng sau.

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;

nhập mô tả hình ảnh ở đây

Tôi muốn có được các giá trị khác không cuối cùng trên các cột val1val2được nhóm theo catvà sắp xếp theo t. Kết quả tôi đang tìm kiếm là

cat  val1 val2
A    1    P
B    10   C

Gần nhất tôi đã đến đang sử dụng LAST_VALUEtrong khi bỏ qua ORDER BYcái sẽ không hoạt động vì tôi cần giá trị không null được đặt hàng cuối cùng.

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

Bảng thực tế có nhiều cột hơn cho cat( cột ngày và cột) và nhiều cột val hơn (cột ngày, chuỗi và số) để chọn giá trị khác không cuối cùng.

Bất kỳ ý tưởng làm thế nào để thực hiện lựa chọn này.


1
@ Vérace Được nhóm theo catthứ tự t.
Edmund

1
@ ypercubeᵀᴹ Không, không có giá trị Q4 bị thiếu, các tgiá trị lặp lại. Nó không phải là dữ liệu ứng xử tốt.
Edmund

4
Được rồi nhưng trong trường hợp đó, bạn phải cung cấp một đơn hàng xác định một đơn hàng hoàn hảo. PARTITION BY cat ORDER BY t, idví dụ. Mặt khác, cùng một truy vấn (bất kỳ truy vấn nào) có thể cung cấp cho bạn các kết quả khác nhau trên các lần thực hiện riêng biệt. Nếu các cột trong bảng chỉ là những cột bạn hiển thị, thì tôi không thấy làm thế nào chúng ta có thể có một thứ tự xác định!
ypercubeᵀᴹ

1
@ ypercubeᵀᴹ Trong đó có thách thức. Không có cột id trong dữ liệu. Có nhiều cột nhóm, một cột chuỗi có thể được sử dụng để sắp xếp theo nhóm và sau đó là nhiều cột giá trị với các giá trị xen kẽ.
Edmund

1
Nếu bạn không thể nói với SQL Server một cách xác định thứ tự các hàng sẽ là gì, làm thế nào bất kỳ người tiêu dùng dữ liệu này sẽ biết sự khác biệt?
Aaron Bertrand

Câu trả lời:


10

Sử dụng kỹ thuật ghép nối từ Câu đố không phải NULL cuối cùng của Itzik Ben Gan sẽ trông như thế này với các kiểu dữ liệu bảng và cột mẫu của bạn.

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;

nhập mô tả hình ảnh ở đây

Một cách khác để viết truy vấn này chia các bước thành CTE để có thể hiển thị tốt hơn những gì đang diễn ra. Nó đưa ra kế hoạch thực hiện chính xác giống như truy vấn ở trên.

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;

Giải pháp này sử dụng thực tế là nối một giá trị null với một cái gì đó dẫn đến một giá trị null. THIẾT LẬP CONCAT_NULL_YIELDS_NULL (Giao dịch-SQL)


Mikael chưng cất rất tốt. Giải pháp này đã giúp tôi tiết kiệm nhiều lần, mặc dù lúc đầu tôi thấy kết thúc bài viết của Itzik rất khó hiểu. Trong đó, ông đã gắn nhãn là "bước 2" khi trong thực tế, nó giống như thực hiện logic phía sau bước 1.
pimbrouwers

2

Chỉ cần thêm một kiểm tra cho NULL trong phân vùng sẽ làm

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

Điều này nên làm điều đó. row_number () và tham gia

Nếu bạn không có một loại tốt, bạn phải hy vọng chỉ một trong số 3 là không có giá trị.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.