วิธีรับค่า non-null ล่าสุดในคอลัมน์ที่เรียงลำดับของตารางขนาดใหญ่ได้อย่างไร


13

ฉันมีอินพุตต่อไปนี้:

 id | value 
----+-------
  1 |   136
  2 |  NULL
  3 |   650
  4 |  NULL
  5 |  NULL
  6 |  NULL
  7 |   954
  8 |  NULL
  9 |   104
 10 |  NULL

ฉันคาดหวังผลลัพธ์ต่อไปนี้:

 id | value 
----+-------
  1 |   136
  2 |   136
  3 |   650
  4 |   650
  5 |   650
  6 |   650
  7 |   954
  8 |   954
  9 |   104
 10 |   104

วิธีแก้ปัญหาเล็กน้อยจะเข้าร่วมตารางที่มี<ความสัมพันธ์แล้วเลือกMAXค่าใน a GROUP BY:

WITH tmp AS (
  SELECT t2.id, MAX(t1.id) AS lastKnownId
  FROM t t1, t t2
  WHERE
    t1.value IS NOT NULL
    AND
    t2.id >= t1.id
  GROUP BY t2.id
)
SELECT
  tmp.id, t.value
FROM t, tmp
WHERE t.id = tmp.lastKnownId;

อย่างไรก็ตามการประมวลผลเล็กน้อยของรหัสนี้จะสร้างตารางภายในของการนับจำนวนแถวของตารางอินพุต ( O (n ^ 2) ) ฉันคาดว่า t-sql จะปรับมันให้เหมาะสม - ในระดับบล็อก / บันทึกงานที่ต้องทำนั้นง่ายมากและเป็นเส้นตรงโดยพื้นฐานคือสำหรับลูป ( O (n) )

อย่างไรก็ตามในการทดลองของฉัน MS SQL 2016 ล่าสุดไม่สามารถปรับให้เหมาะสมกับแบบสอบถามนี้ได้อย่างถูกต้องทำให้แบบสอบถามนี้ไม่สามารถดำเนินการสำหรับตารางอินพุตขนาดใหญ่

นอกจากนี้แบบสอบถามต้องทำงานอย่างรวดเร็วทำให้โซลูชันเคอร์เซอร์ที่ใช้ง่าย (แต่แตกต่างกันมาก) เป็นไปไม่ได้

การใช้ตารางชั่วคราวที่ได้รับการสนับสนุนจากหน่วยความจำอาจเป็นการประนีประนอมที่ดี แต่ฉันไม่แน่ใจว่าจะสามารถทำงานได้เร็วขึ้นอย่างมากหรือไม่พิจารณาว่าแบบสอบถามตัวอย่างของฉันที่ใช้แบบสอบถามย่อยไม่ทำงาน

ฉันยังคิดที่จะขุดฟังก์ชั่นหน้าต่างบางส่วนจากเอกสาร t-sql สิ่งที่อาจถูกหลอกให้ทำสิ่งที่ฉันต้องการ ตัวอย่างเช่นผลรวมสะสมกำลังทำคล้ายกันมาก แต่ฉันไม่สามารถหลอกให้องค์ประกอบที่ไม่ใช่โมฆะล่าสุดและไม่รวมผลรวมขององค์ประกอบก่อนหน้านี้

ทางออกที่ดีที่สุดคือการสืบค้นอย่างรวดเร็วโดยไม่ต้องใช้รหัสขั้นตอนหรือตารางชั่วคราว อีกวิธีหนึ่งโซลูชันที่มีตารางชั่วคราวก็ไม่เป็นไร แต่การทำซ้ำขั้นตอนของตารางจะไม่เป็นเช่นนั้น

คำตอบ:


12

วิธีแก้ปัญหาทั่วไปสำหรับปัญหานี้ได้รับจาก Itzik Ben-Gan ในบทความของเขาThe Last non NULL Puzzle :

DROP TABLE IF EXISTS dbo.Example;

CREATE TABLE dbo.Example
(
    id integer PRIMARY KEY,
    val integer NULL
);

INSERT dbo.Example
    (id, val)
VALUES
    (1, 136),
    (2, NULL),
    (3, 650),
    (4, NULL),
    (5, NULL),
    (6, NULL),
    (7, 954),
    (8, NULL),
    (9, 104),
    (10, NULL);

SELECT
    E.id,
    E.val,
    lastval =
        CAST(
            SUBSTRING(
                MAX(CAST(E.id AS binary(4)) + CAST(E.val AS binary(4))) OVER (
                    ORDER BY E.id
                    ROWS UNBOUNDED PRECEDING),
            5, 4)
        AS integer)
FROM dbo.Example AS E
ORDER BY
    E.id;

การสาธิต: db <> ซอ


11

ฉันคาดว่า t-sql จะปรับมันให้เหมาะสม - ในระดับบล็อก / บันทึกงานที่ต้องทำนั้นง่ายมากและเป็นเส้นตรงโดยพื้นฐานคือสำหรับลูป (O (n))

นั่นไม่ใช่แบบสอบถามที่คุณเขียน มันอาจจะไม่เท่ากับแบบสอบถามที่คุณเขียนขึ้นอยู่กับรายละเอียดเล็ก ๆ น้อย ๆ ของสคีมาตาราง คุณคาดหวังมากเกินไปจากเครื่องมือเพิ่มประสิทธิภาพข้อความค้นหา

ด้วยการจัดทำดัชนีที่ถูกต้องคุณจะได้รับอัลกอริทึมที่คุณค้นหาผ่าน T-SQL ต่อไปนี้:

SELECT t1.id, ca.[VALUE] 
FROM dbo.[BIG_TABLE(FOR_U)] t1
CROSS APPLY (
    SELECT TOP (1) [VALUE]
    FROM dbo.[BIG_TABLE(FOR_U)] t2
    WHERE t2.ID <= t1.ID AND t2.[VALUE] IS NOT NULL
    ORDER BY t2.ID DESC
) ca; --ORDER BY t1.ID ASC

สำหรับแต่ละแถวประมวลผลแบบสอบถามลัดเลาะไปข้างหลังดัชนีและหยุดเมื่อพบแถวที่มีค่า Null [VALUE]ไม่ใช่สำหรับ บนเครื่องของฉันเสร็จสิ้นในประมาณ 90 วินาทีสำหรับ 100 ล้านแถวในตารางต้นทาง คิวรีรันนานกว่าที่จำเป็นเนื่องจากจำนวนเวลาจะสูญเปล่าบนไคลเอ็นต์ที่ละทิ้งแถวเหล่านั้นทั้งหมด

ไม่ชัดเจนสำหรับฉันหากคุณต้องการผลลัพธ์ที่สั่งหรือสิ่งที่คุณวางแผนจะทำกับชุดผลลัพธ์ขนาดใหญ่เช่นนั้น สามารถปรับแบบสอบถามให้ตรงกับสถานการณ์จริงได้ ข้อได้เปรียบที่ใหญ่ที่สุดของวิธีนี้คือไม่จำเป็นต้องเรียงลำดับในแผนคิวรี ที่สามารถช่วยให้ชุดผลลัพธ์มีขนาดใหญ่ขึ้น ข้อเสียอย่างหนึ่งคือประสิทธิภาพจะไม่เหมาะสมหากมี NULL จำนวนมากในตารางเนื่องจากแถวจำนวนมากจะถูกอ่านจากดัชนีและถูกละทิ้ง คุณควรจะสามารถปรับปรุงประสิทธิภาพด้วยดัชนีตัวกรองที่แยก NULLs สำหรับกรณีนั้น

ข้อมูลตัวอย่างสำหรับการทดสอบ:

DROP TABLE IF EXISTS #t;

CREATE TABLE #t (
ID BIGINT NOT NULL
);

INSERT INTO #t WITH (TABLOCK)
SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

DROP TABLE IF EXISTS dbo.[BIG_TABLE(FOR_U)];

CREATE TABLE dbo.[BIG_TABLE(FOR_U)] (
ID BIGINT NOT NULL,
[VALUE] BIGINT NULL
);

INSERT INTO dbo.[BIG_TABLE(FOR_U)] WITH (TABLOCK)
SELECT 10000 * t1.ID + t2.ID, CASE WHEN (t1.ID + t2.ID) % 3 = 1 THEN t2.ID ELSE NULL END
FROM #t t1
CROSS JOIN #t t2;

CREATE UNIQUE CLUSTERED INDEX ADD_ORDERING ON dbo.[BIG_TABLE(FOR_U)] (ID);

7

วิธีการหนึ่งโดยการใช้OVER()และMAX()และCOUNT()ขึ้นอยู่กับแหล่งนี้อาจจะ:

SELECT ID, MAX(value) OVER (PARTITION BY Value2) as value
FROM
(
    SELECT ID, value
        ,COUNT(value) OVER (ORDER BY ID) AS Value2
    FROM dbo.HugeTable
) a
ORDER BY ID;

ผลลัพธ์

Id  UpdatedValue
1   136
2   136
3   650
4   650
5   650
6   650
7   954
8   954
9   104
10  104

วิธีการอื่นที่อ้างอิงแหล่งข้อมูลนี้เกี่ยวข้องอย่างใกล้ชิดกับตัวอย่างแรก

;WITH CTE As 
( 
SELECT  value,
        Id, 
        COUNT(value) 
        OVER(ORDER BY Id) As  Value2 
FROM dbo.HugeTable
),

CTE2 AS ( 
SELECT Id,
       value,
       First_Value(value)  
       OVER( PARTITION BY Value2
             ORDER BY Id) As UpdatedValue 
FROM CTE 
            ) 
SELECT Id,UpdatedValue 
FROM CTE2;

3
พิจารณาเพิ่มรายละเอียดเกี่ยวกับวิธีการทำงานของวิธีการเหล่านี้ด้วย "ตารางขนาดใหญ่"
โจ Obbish
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.