วิธีการเลือกชุดของค่าที่ไม่ใช่ NULL ล่าสุดต่อคอลัมน์ในกลุ่ม?


9

ฉันใช้ SQL Server 2016 และข้อมูลที่ฉันใช้อยู่มีแบบฟอร์มต่อไปนี้

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;

ป้อนคำอธิบายรูปภาพที่นี่

ฉันต้องการขอรับค่าที่ไม่ใช่ null สุดท้ายมากกว่าคอลัมน์val1และval2จัดกลุ่มตามและได้รับคำสั่งจากcat tผลลัพธ์ที่ฉันต้องการคือ

cat  val1 val2
A    1    P
B    10   C

สิ่งที่ฉันเข้ามาใกล้ที่สุดคือการใช้LAST_VALUEขณะที่เพิกเฉยสิ่งORDER BYที่ไม่สามารถใช้งานได้เนื่องจากฉันต้องการค่าที่ไม่ใช่ค่าว่างที่สั่งล่าสุด

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

ตารางจริงมีคอลัมน์มากขึ้นสำหรับcat( คอลัมน์วันที่และสตริง) และคอลัมน์ val เพิ่มเติม (คอลัมน์วันที่, สตริงและตัวเลข) เพื่อเลือกค่าที่ไม่เป็นค่าสุดท้าย

ความคิดใด ๆ วิธีการเลือกนี้


1
@ Véraceกลุ่มโดยได้รับคำสั่งจากcat t
Edmund

1
@ ypercubeᵀᴹไม่ไม่มีค่า Q4 ที่ขาดหายไปtค่าซ้ำ มันเป็นข้อมูลที่ไม่ประพฤติดี
Edmund

4
ถูกต้อง แต่ในกรณีนั้นคุณต้องจัดทำคำสั่งซื้อที่กำหนดลำดับที่สมบูรณ์แบบ PARTITION BY cat ORDER BY t, idตัวอย่างเช่น. มิฉะนั้นข้อความค้นหาเดียวกัน (แบบสอบถามใด ๆ ) อาจให้ผลลัพธ์ที่แตกต่างกันสำหรับการดำเนินการแยกต่างหาก หากคอลัมน์ในตารางเป็นเพียงคอลัมน์ที่คุณแสดงฉันไม่เห็นว่าเราจะมีคำสั่งที่แน่นอนได้อย่างไร!
ypercubeᵀᴹ

1
@ ypercubeᵀᴹความท้าทายอยู่ตรงนั้น ไม่มีคอลัมน์ id ในข้อมูล มีคอลัมน์การจัดกลุ่มหลายคอลัมน์คอลัมน์ที่สามารถใช้สำหรับภายในการสั่งซื้อกลุ่มแล้วคอลัมน์ค่าหลายคอลัมน์ที่มี nulls กระจาย
Edmund

1
ถ้าคุณไม่สามารถบอก SQL Server ได้ว่าอะไรคือลำดับของแถวควรจะเป็นอย่างไรผู้บริโภคของข้อมูลนี้จะทราบถึงความแตกต่างได้อย่างไร
Aaron Bertrand

คำตอบ:


10

การใช้เทคนิคการต่อข้อมูลจากปริศนาที่ไม่ใช่ NULL ล่าสุดโดย Itzik Ben Gan จะมีลักษณะเช่นนี้กับตารางตัวอย่างและชนิดข้อมูลคอลัมน์ของคุณ

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;

ป้อนคำอธิบายรูปภาพที่นี่

อีกวิธีในการเขียนคิวรีนี้ที่แบ่งขั้นตอนเป็น CTE เพื่อแสดงว่าเกิดอะไรขึ้น มันให้แผนปฏิบัติการที่แน่นอนเหมือนกับแบบสอบถามข้างต้น

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;

วิธีนี้ใช้ความจริงที่ว่าการเชื่อมต่อค่า Null กับบางสิ่งส่งผลให้เกิดค่า Null SET CONCAT_NULL_YIELDS_NULL (Transact-SQL)


มิเคลกลั่นได้ดีมาก วิธีนี้ช่วยฉันได้หลายครั้งแม้ว่าฉันจะพบว่าตอนจบของบทความของ Itzik เกิดความสับสนในตอนแรก ในที่ที่เขาระบุว่ามัน "ขั้นตอนที่ 2" เมื่อในความเป็นจริงมันเป็นเหมือนการใช้ตรรกะที่อยู่เบื้องหลังขั้นตอนที่ 1
pimbrouwers

2

เพียงเพิ่มการตรวจสอบสำหรับ NULL ในพาร์ติชันที่จะทำ

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

สิ่งนี้ควรทำ row_number () และการเข้าร่วม

หากคุณไม่มีการเรียงลำดับที่ดีคุณต้องหวังว่าหนึ่งในไตรมาสที่ 3 จะไม่เป็นโมฆะ

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.