วิธีที่มีประสิทธิภาพที่สุดในการรับจำนวนคอลัมน์ขั้นต่ำใน SQL Server 2005 คืออะไร


29

ฉันอยู่ในสถานการณ์ที่ฉันต้องการรับค่าต่ำสุดจาก 6 คอลัมน์

ฉันได้พบสามวิธีในการบรรลุเป้าหมายนี้ แต่ฉันมีความกังวลเกี่ยวกับประสิทธิภาพของวิธีการเหล่านี้และต้องการทราบว่าวิธีไหนที่จะดีกว่าสำหรับการแสดง

วิธีแรกคือการใช้คำสั่งคดีใหญ่ นี่คือตัวอย่างที่มี 3 คอลัมน์ตามตัวอย่างในลิงก์ด้านบน คำสั่ง case ของฉันจะนานกว่านี้เพราะฉันจะดูที่ 6 คอลัมน์

Select Id,
       Case When Col1 <= Col2 And Col1 <= Col3 Then Col1
            When Col2 <= Col3 Then Col2 
            Else Col3
            End As TheMin
From   MyTable

ตัวเลือกที่สองคือการใช้ประกอบกับงบเลือกหลายUNION ฉันจะใส่สิ่งนี้ใน UDF ที่ยอมรับพารามิเตอร์ Id

select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable

และ

select min(col)
from
(
    select col1 [col] from MyTable where Id = @id
    union all
    select col2 from MyTable where Id = @id
    union all
    select col3 from MyTable where Id = @id
) as t

และตัวเลือกที่ 3 ที่ฉันพบคือใช้ตัวดำเนินการ UNPIVOTซึ่งฉันไม่รู้ด้วยซ้ำจนกระทั่งตอนนี้

with cte (ID, Col1, Col2, Col3)
as
(
    select ID, Col1, Col2, Col3
    from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
    select
        ID, min(Amount) as TheMin
    from 
        cte 
        UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
    group by ID
) as minValues
on cte.ID = minValues.ID

เนื่องจากขนาดของตารางและความถี่ที่มีการสอบถามและอัปเดตตารางนี้ฉันจึงกังวลเกี่ยวกับผลกระทบของประสิทธิภาพการทำงานที่แบบสอบถามเหล่านี้จะมีในฐานข้อมูล

จริง ๆ แล้วแบบสอบถามนี้จะถูกใช้ในการเข้าร่วมกับตารางที่มีระเบียนไม่กี่ล้านระเบียนอย่างไรก็ตามระเบียนที่ส่งคืนจะถูกลดลงเป็นร้อยระเบียนในแต่ละครั้ง มันจะทำงานหลายครั้งตลอดทั้งวันและ 6 คอลัมน์ที่ฉันสืบค้นมีการอัปเดตบ่อยครั้ง (มีสถิติรายวัน) ฉันไม่คิดว่าจะมีดัชนีใด ๆ ในคอลัมน์ 6 คอลัมน์ที่ฉันกำลังสืบค้น

วิธีใดในวิธีเหล่านี้จะดีกว่าสำหรับประสิทธิภาพเมื่อพยายามรับขั้นต่ำของหลายคอลัมน์ หรือมีวิธีอื่นที่ดีกว่าที่ฉันไม่รู้

ฉันใช้ SQL Server 2005

ข้อมูลตัวอย่างและผลลัพธ์

หากข้อมูลของฉันมีบันทึกเช่นนี้:

Id Col1 Col2 Col3 Col4 Col5 Col6
1 3 4 0 2 1 5
2 2 6 10 5 7 9
3 1 1 2 3 4 5
4 9 5 4 6 8 9 9

ผลลัพธ์ที่ได้ควรจะเป็น

รหัสประจำตัว
1 0
2 2
3 1
4 4

คำตอบ:


22

ฉันทดสอบประสิทธิภาพของวิธีการทั้ง 3 วิธีและนี่คือสิ่งที่ฉันพบ:

  • 1 บันทึก: ไม่มีความแตกต่างที่เห็นได้ชัดเจน
  • 10 บันทึก: ไม่มีความแตกต่างที่เห็นได้ชัดเจน
  • 1,000 บันทึก: ไม่มีความแตกต่างที่เห็นได้ชัดเจน
  • 10,000 บันทึก: UNIONแบบสอบถามย่อยช้าลงเล็กน้อย CASE WHENแบบสอบถามเป็นเพียงเล็กน้อยเร็วกว่าUNPIVOTหนึ่ง
  • 100,000 บันทึก: UNIONแบบสอบถามย่อยช้าลงอย่างมาก แต่UNPIVOTแบบสอบถามกลายเป็นเร็วกว่าCASE WHENแบบสอบถามเล็กน้อย
  • 500,000 บันทึก: UNIONแบบสอบถามย่อยยังคงช้าลงอย่างมีนัยสำคัญ แต่UNPIVOTกลายเป็นเร็วกว่าCASE WHENแบบสอบถาม

ดังนั้นผลลัพธ์สุดท้ายจึงน่าจะเป็น

  • ด้วยชุดระเบียนที่เล็กลงดูเหมือนว่าจะไม่แตกต่างกันพอกับเรื่อง ใช้สิ่งที่ง่ายที่สุดในการอ่านและบำรุงรักษา

  • เมื่อคุณเริ่มเข้าสู่ชุดระเบียนที่มีขนาดใหญ่ขึ้นUNION ALLแบบสอบถามย่อยจะเริ่มทำงานได้ไม่ดีเมื่อเทียบกับอีกสองวิธี

  • CASEงบดำเนินการที่ดีที่สุดขึ้นจนถึงจุดหนึ่ง (ในกรณีของฉันประมาณ 100k แถว) และที่จุดUNPIVOTแบบสอบถามกลายเป็นแบบสอบถามที่มีประสิทธิภาพที่สุด

จำนวนจริงที่ข้อความค้นหาหนึ่งดีกว่าคำถามอื่นอาจเปลี่ยนไปเนื่องจากฮาร์ดแวร์สคีมาฐานข้อมูลและโหลดเซิร์ฟเวอร์ปัจจุบันของคุณดังนั้นโปรดทดสอบกับระบบของคุณเองหากคุณกังวลเกี่ยวกับประสิทธิภาพ

ฉันยังวิ่งทดสอบบางอย่างใช้คำตอบของมิคาเอล ; อย่างไรก็ตามมันช้ากว่าวิธีอื่นทั้งสามที่ลองใช้ที่นี่สำหรับขนาดชุดระเบียนส่วนใหญ่ ข้อยกเว้นเพียงอย่างเดียวคือมันทำได้ดีกว่าUNION ALLแบบสอบถามสำหรับขนาดชุดระเบียนที่มีขนาดใหญ่มาก ฉันชอบความจริงที่มันแสดงชื่อคอลัมน์เพิ่มเติมจากค่าที่น้อยที่สุด

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


6

ไม่ทราบเกี่ยวกับสิ่งที่เร็วที่สุด แต่คุณสามารถลองสิ่งนี้

declare @T table
(
  Col1 int,
  Col2 int,
  Col3 int,
  Col4 int,
  Col5 int,
  Col6 int
)

insert into @T values(1, 2, 3, 4, 5, 6)
insert into @T values(2, 3, 1, 4, 5, 6)

select T4.ColName, T4.ColValue
from @T as T1
  cross apply (
                select T3.ColValue, T3.ColName
                from (
                       select row_number() over(order by T2.ColValue) as rn,
                              T2.ColValue,
                              T2.ColName
                       from (
                              select T1.Col1, 'Col1' union all
                              select T1.Col2, 'Col2' union all
                              select T1.Col3, 'Col3' union all
                              select T1.Col4, 'Col4' union all
                              select T1.Col5, 'Col5' union all
                              select T1.Col6, 'Col6'
                            ) as T2(ColValue, ColName)
                     ) as T3
                where T3.rn = 1
              ) as T4

ผล:

ColName ColValue
------- -----------
Col1    1
Col3    1

หากคุณไม่สนใจว่าคอลัมน์ใดมีค่าขั้นต่ำคุณสามารถใช้สิ่งนี้แทน

declare @T table
(
  Id int,
  Col1 int,
  Col2 int,
  Col3 int,
  Col4 int,
  Col5 int,
  Col6 int
)

insert into @T
select 1,        3,       4,       0,       2,       1,       5 union all
select 2,        2,       6,      10,       5,       7,       9 union all
select 3,        1,       1,       2,       3,       4,       5 union all
select 4,        9,       5,       4,       6,       8,       9

select T.Id, (select min(T1.ColValue)
              from (
                      select T.Col1 union all
                      select T.Col2 union all
                      select T.Col3 union all
                      select T.Col4 union all
                      select T.Col5 union all
                      select T.Col6
                    ) as T1(ColValue)
             ) as ColValue
from @T as T

แบบสอบถาม unpivot ที่เรียบง่าย

select Id, min(ColValue) as ColValue
from @T
unpivot (ColValue for Col in (Col1, Col2, Col3, Col4, Col5, Col6)) as U
group by Id

6

เพิ่มคอลัมน์ที่คำนวณแล้วที่ยังคงใช้CASEคำสั่งเพื่อทำตรรกะที่คุณต้องการ

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

ค่าจะถูกคำนวณใหม่ทุกครั้งที่มีการเปลี่ยนแปลงค่าต้นทาง ( INSERT/ UPDATE/ MERGE) ฉันไม่ได้พูดนี้คือจำเป็นต้องเป็นทางออกที่ดีที่สุดสำหรับภาระงานที่ฉันเพียงแค่ให้มันเป็นวิธีการแก้ปัญหาเช่นเดียวกับคำตอบอื่น ๆ เฉพาะ OP เท่านั้นที่สามารถกำหนดว่าเวิร์กโหลดใดที่ดีที่สุด


1

คำชี้แจงกรณีสำหรับ 6 วัน หากต้องการทำน้อยให้คัดลอกสาขาจริงจากคำสั่งกรณีแรก กรณีที่แย่ที่สุดคือเมื่อ Date1 เป็นค่าต่ำสุดกรณีที่ดีที่สุดคือเมื่อ Date6 เป็นค่าต่ำสุดดังนั้นให้ใส่วันที่ที่เป็นไปได้มากที่สุดใน Date6 ฉันเขียนสิ่งนี้เนื่องจากข้อ จำกัด ของคอลัมน์ที่คำนวณ

CASE WHEN Date1 IS NULL OR Date1 > Date2 THEN
        CASE WHEN Date2 IS NULL OR Date2 > Date3 THEN
            CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
                CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                        Date6
                    ELSE
                        Date4
                    END
                END
            ELSE
                CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
                        Date6
                    ELSE
                        Date3
                    END
                END
            END
        ELSE
            CASE WHEN Date2 IS NULL OR Date2 > Date4 THEN
                CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                        CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                            Date6
                        ELSE
                            Date5
                        END
                    ELSE
                        CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                            Date6
                        ELSE
                            Date4
                        END
                    END
                END
            ELSE
                CASE WHEN Date2 IS NULL OR Date2 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date2 IS NULL OR Date2 > Date6 THEN
                        Date6
                    ELSE
                        Date2
                    END
                END
            END
        END
ELSE
    CASE WHEN Date1 IS NULL OR Date1 > Date3 THEN
        CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
            CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                    Date6
                ELSE
                    Date4
                END
            END
        ELSE
            CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
                    Date6
                ELSE
                    Date3
                END
            END
        END
    ELSE
        CASE WHEN Date1 IS NULL OR Date1 > Date4 THEN
            CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                    Date6
                ELSE
                    Date4
                END
            END
        ELSE
            CASE WHEN Date1 IS NULL OR Date1 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date1 IS NULL OR Date1 > Date6 THEN
                    Date6
                ELSE
                    Date1
                END
            END
        END
    END
END

หากคุณพบหน้านี้เพียงแค่เปรียบเทียบวันที่และไม่เกี่ยวข้องกับประสิทธิภาพหรือความเข้ากันได้คุณสามารถใช้ Table Value Constructor ซึ่งสามารถใช้ได้ทุกที่ที่อนุญาตการเลือกย่อย (SQL Server 2008 ขึ้นไป):

Lowest =    
(
    SELECT MIN(TVC.d) 
    FROM 
    (
        VALUES
            (Date1), 
            (Date2), 
            (Date3), 
            (Date4), 
            (Date5), 
            (Date6)
    ) 
    AS TVC(d)
)

1

ใบแจ้งยอดของคุณcaseไม่มีประสิทธิภาพ คุณกำลังทำการเปรียบเทียบ 5 รายการในกรณีที่แย่ที่สุดและอีก 2 กรณีที่ดีที่สุด ในขณะที่การค้นหาขั้นต่ำnควรทำที่n-1การเปรียบเทียบมากที่สุด

สำหรับแต่ละแถวโดยเฉลี่ยคุณทำการเปรียบเทียบ 3.5 แทนที่จะเป็น 2 ดังนั้นจึงใช้เวลา cpu มากขึ้นและช้า ลองทดสอบอีกครั้งโดยใช้caseข้อความด้านล่าง มันเป็นเพียงแค่ใช้ 2 รถต่อแถวและควรจะมีประสิทธิภาพมากกว่าและunpivotunion all

Select Id, 
       Case 
           When Col1 <= Col2 then case when Col1 <= Col3 Then Col1  else col3 end
            When  Col2 <= Col3 Then Col2  
            Else Col3 
            End As TheMin 
From   YourTableNameHere

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

Unpivotเป็นสิ่งที่ดีและให้ลองไป Unpivot (select 1 union all select 2 union all select 3)คู่มือเช่นกันโดยใช้ข้ามเข้าร่วมโต๊ะของคุณด้วย unpivotมันควรจะเป็นที่มีประสิทธิภาพเป็น

ทางออกที่ดีที่สุดคือการมีคอลัมน์ยืนยันที่คำนวณได้หากคุณไม่มีปัญหาเกี่ยวกับพื้นที่ มันจะเพิ่มขนาดของแถวทีละ 4 ไบต์ (ฉันเดาว่าคุณจะมีintประเภท) ซึ่งจะเพิ่มขนาดของตาราง

อย่างไรก็ตามพื้นที่และหน่วยความจำมีปัญหาในระบบของคุณและ CPU ไม่ได้ทำให้มันคงอยู่ แต่ใช้คอลัมน์ที่คำนวณได้ง่ายโดยใช้คำสั่ง case มันจะทำให้รหัสง่ายขึ้น


-1

ฉันเดาว่าตัวเลือกแรกนั้นเร็วที่สุด (แม้ว่ามันจะดูไม่ลื่นเลยจากมุมมองการเขียนโปรแกรม!) นี่เป็นเพราะมันเกี่ยวข้องกับแถว N แน่นอน (โดยที่ N คือขนาดตาราง) และไม่ต้องค้นหาหรือเรียงลำดับเช่นวิธีที่ 2 หรือ 3

การทดสอบที่มีตัวอย่างจำนวนมากควรพิสูจน์จุดนั้น

ในฐานะที่เป็นอีกหนึ่งตัวเลือกในการพิจารณา (ราวกับว่าคุณต้องการมากขึ้น!) คือการสร้างมุมมองที่ปรากฏบนตารางของคุณ หากขนาดตารางของคุณอยู่ใน 100s พันหรือมากกว่า ด้วยวิธีนี้ค่า min จะถูกคำนวณในขณะที่แถวถูกเปลี่ยนและตารางทั้งหมดจะไม่ต้องถูกประมวลผลกับทุกแบบสอบถาม ใน SQL Server มุมมองที่ปรากฏขึ้นจะเรียกว่ามุมมองที่จัดทำดัชนี


-1
Create table #temp
   (
    id int identity(1,1),
    Name varchar(30),
    Year1 int,
    Year2 int,
    Year3 int,
    Year4 int
   )

   Insert into #temp values ('A' ,2015,2016,2014,2010)
   Insert into #temp values ('B' ,2016,2013,2017,2018)
   Insert into #temp values ('C' ,2010,2016,2014,2017)
   Insert into #temp values ('D' ,2017,2016,2014,2015)
   Insert into #temp values ('E' ,2016,2016,2016,2016)
   Insert into #temp values ('F' ,2016,2017,2018,2019)
   Insert into #temp values ('G' ,2016,2017,2020,2019)

   Select *, Case 
                 when Year1 >= Year2 and Year1 >= Year3 and Year1 >= Year4 then Year1
                 when Year2 >= Year3 and Year2 >= Year4 and Year2 >= Year1 then Year2
                 when Year3 >= Year4 and Year3 >= Year1 and Year3 >= Year2 then Year3
                 when Year4 >= Year1 and Year4 >= Year2 and Year4 >= Year3 then Year4  
                 else Year1 end as maxscore  
                 from #temp

คุณไม่ได้กำลังคิดค่า NULL ซึ่งทำให้การแสดงออกของ CASE ค่อนข้างง่าย อย่างไรก็ตามหากคอลัมน์อย่างน้อยหนึ่งคอลัมน์เป็น NULL โซลูชันของคุณจะกลับมาYear1เป็นผลลัพธ์ซึ่งอาจไม่ถูกต้อง
Andriy M
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.