ใน SQL Server ฉันสามารถรับประกันการสั่งซื้อโดยไม่มีคำสั่ง ORDER BY อย่างชัดเจนเมื่อการค้นหาดัชนีถูกบังคับบนตารางที่มีเพียงดัชนีคลัสเตอร์เท่านั้น


24

อัปเดต 2014-12-18

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

เป็นต้นฉบับ

คำถามนี้เกิดขึ้นเพราะวิธีแก้ปัญหาที่เร็วที่สุดเพียงอย่างเดียวที่ฉันสามารถพบกับปัญหาเฉพาะนั้นใช้ได้โดยไม่มีORDER BYข้อ ด้านล่างนี้เป็น T-SQL แบบเต็มรูปแบบที่จำเป็นในการสร้างปัญหาพร้อมกับวิธีแก้ไขปัญหาที่เสนอของฉัน (ฉันใช้ SQL Server 2008 R2 ถ้าเป็นเช่นนั้น)

--Create Orders table
IF OBJECT_ID('tempdb..#Orders') IS NOT NULL DROP TABLE #Orders
CREATE TABLE #Orders
(  
       OrderID    INT NOT NULL IDENTITY(1,1)
     , CustID     INT NOT NULL
     , StoreID    INT NOT NULL       
     , Amount     FLOAT NOT NULL
)
CREATE CLUSTERED INDEX IX ON #Orders (StoreID, Amount DESC, CustID)

--Add 1 million rows w/ 100K Customers each of whom had 10 orders
;WITH  
    Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows  
    Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows 
    Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows 
    Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows 
    Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows 
    FinalCte AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Cte5)
INSERT INTO #Orders (CustID, StoreID, Amount)
SELECT CustID = Number / 10
     , StoreID    = Number % 4
     , Amount     = 1000 * RAND(Number)
FROM  FinalCte
WHERE Number <= 1000000

SET STATISTICS IO ON
SET STATISTICS TIME ON

--For StoreID = 1, find the top 500 customers ordered by their most expensive purchase (Amount)

--Solution A: Without ORDER BY
DECLARE @Top INT = 500
SELECT DISTINCT TOP (@Top) CustID
FROM #Orders WITH(FORCESEEK)
WHERE StoreID = 1
OPTION(OPTIMIZE FOR (@Top = 1), FAST 1);
--9 logical reads, CPU Time = 0 ms, elapsed time = 1 ms
GO
--Solution B: With ORDER BY
DECLARE @Top INT = 500
SELECT TOP (@Top) CustID
FROM #Orders
WHERE StoreID = 1
GROUP BY CustID
ORDER BY MAX(Amount) DESC
OPTION(MAXDOP 1)
--745 logical reads, CPU Time = 141 ms, elapsed time = 145 ms
--Uses Sort operator

GO

นี่คือแผนการดำเนินการสำหรับโซลูชัน A และ B ตามลำดับ:

โซล

โซลบี

โซลูชัน A ให้ประสิทธิภาพที่ฉันต้องการ แต่ฉันไม่สามารถทำให้มันทำงานได้เหมือนกันเมื่อเพิ่ม ORDER BY ประเภทใด ๆ (เช่นดูโซลูชัน B) และแน่นอนว่าโซลูชัน A ต้องส่งผลลัพธ์ตามลำดับเนื่องจาก 1) ตารางมีดัชนีเพียงหนึ่งดัชนีเท่านั้น 2) การค้นหาถูกบังคับจึงช่วยลดความเป็นไปได้ที่จะใช้การสแกนตามลำดับการจัดสรรตามหน้า IAM .

ดังนั้นคำถามของฉันคือ:

  1. ฉันถูกที่จะรับประกันการสั่งซื้อในกรณีนี้โดยไม่มีคำสั่งตามข้อ?

  2. ถ้าไม่มีวิธีอื่นในการบังคับแผนที่เร็วเท่ากับโซลูชัน A โดยเฉพาะอย่างยิ่งวิธีที่หลีกเลี่ยงการเรียงลำดับหรือไม่ โปรดทราบว่ามันจะต้องแก้ปัญหาเดียวกันแน่นอน ( StoreID = 1หาลูกค้า 500 อันดับแรกที่สั่งซื้อด้วยจำนวนการซื้อที่แพงที่สุด) มันจะต้องยังคงใช้#Ordersตาราง แต่รูปแบบการจัดทำดัชนีที่แตกต่างกันจะตกลง


16
ORDER BYการสั่งซื้อมีการประกันเท่านั้นถ้าคุณใช้
alroc

8
" ฉันถูกต้องหรือไม่ที่จะรับประกันการสั่งซื้อในกรณีนี้โดยไม่มีคำสั่ง " - ไม่อย่างแน่นอน
a_horse_with_no_name

3
นี่คือบทความที่อธิบายได้ดีมาก blogs.msdn.com/b/conor_cunningham_msft/archive/2008/08/27/…
Sean Lange

@SeanLange: เหมือนกับคุณและคนอื่น ๆ ฉันไม่สบายใจที่จะออกจากการสั่งซื้อด้วยเหตุผลเดียวกันทั้งหมด อย่างไรก็ตามก) ฉันไม่สามารถค้นหาข้อความค้นหาที่มีประสิทธิภาพเช่นเดียวกับโซลูชัน A ที่ใช้ ORDER BY และ b) ฉันไม่รู้วิธีที่จะสามารถสั่งซื้อได้อย่างไม่ถูกต้อง คุณ ฉันไม่ได้บอกว่าไม่มีทางฉันแค่ไม่รู้เรื่องหนึ่งและหวังว่าจะมีใครสามารถพูดได้ถ้ามันมีอยู่จริง แม้แต่ตัวอย่างในบทความที่คุณอ้างอิงจะใช้กับการสแกนที่ไม่ต้องการ
JohnnyM

UPDATE: ฉันเปลี่ยนประเภทข้อมูลจำนวน & วิธีการคำนวณเพื่อหลีกเลี่ยงการซ้ำซ้อนมากมาย หลักการทั้งหมดยังคงใช้ แม้ว่าในปัญหานี้ฉันไม่สนใจว่าใครจะชนะเมื่อมีเน็คไท แต่การมีความสัมพันธ์จำนวนมากทำให้มันยากที่จะดูว่าเกิดอะไรขึ้นเมื่อดูข้อมูล เป็นที่ชัดเจนมากขึ้นในขณะนี้ยกเว้นความสัมพันธ์ Solution A และ B ให้ผลลัพธ์ที่เหมือนกัน
JohnnyM

คำตอบ:


23
  1. ฉันถูกที่จะรับประกันการสั่งซื้อในกรณีนี้โดยไม่มีคำสั่งตามข้อ?

ไม่ได้มีการใช้Flow Distinctที่รักษาคำสั่งซื้อ (อนุญาตORDER BYโดยไม่มีการเรียงลำดับ) ใน SQL Server วันนี้ เป็นไปได้ที่จะทำในหลักการ แต่ก็มีหลายสิ่งที่เป็นไปได้ถ้าเราได้รับอนุญาตให้เปลี่ยนซอร์สโค้ดเซิร์ฟเวอร์ SQL ถ้าคุณสามารถทำให้กรณีที่ดีสำหรับการทำงานการพัฒนานี้คุณอาจจะแนะนำให้ไมโครซอฟท์

  1. ถ้าไม่มีวิธีอื่นในการบังคับแผนที่เร็วเท่ากับโซลูชัน A หรือไม่โดยเฉพาะอย่างยิ่งวิธีที่หลีกเลี่ยงการเรียงลำดับหรือไม่?

ใช่. (คำแนะนำตาราง & แบบสอบถามจำเป็นต้องใช้เฉพาะเมื่อใช้เครื่องมือประมาณค่า cardinality ก่อนปี 2014):

-- Additional index
CREATE UNIQUE NONCLUSTERED INDEX i 
ON #Orders (StoreID, CustID, Amount, OrderID);

-- Query
SELECT TOP (500) 
    O.CustID, 
    O.Amount
FROM #Orders AS O
    WITH (FORCESEEK(IX (StoreID)))
WHERE O.StoreID = 1
AND NOT EXISTS
(
    SELECT NULL
    FROM #Orders AS O2
        WITH (FORCESEEK(i (StoreID, CustID, Amount)))
    WHERE 
        O2.StoreID = O.StoreID
        AND O2.CustID = O.CustID
        AND O2.Amount >= O.Amount
        AND
        (
            O2.Amount > O.Amount
            OR
            (
                O2.Amount = O.Amount
                AND O2.OrderID > O.OrderID
            )
        )
)
ORDER BY
    O.Amount DESC
OPTION (MAXDOP 1);

แผนปฏิบัติการจริง

(500 row(s) affected)

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 4 ms.

โซลูชัน SQL CLR

สคริปต์ต่อไปนี้แสดงให้เห็นถึงการใช้ฟังก์ชั่นที่มีค่าเป็นตาราง SQL CLR เพื่อตอบสนองความต้องการที่ระบุไว้ ฉันไม่ใช่ผู้เชี่ยวชาญ C # ดังนั้นรหัสอาจมีการปรับปรุง:

USE Sandpit;
GO
-- Ensure SQLCLR is enabled
EXECUTE sys.sp_configure
    @configname = 'clr enabled',
    @configvalue = 1;
RECONFIGURE;
GO
-- Lazy, but effective to allow EXTERNAL_ACCESS
ALTER DATABASE Sandpit
SET TRUSTWORTHY ON;
GO
-- The CLR assembly
CREATE ASSEMBLY FlowDistinctOrder
AUTHORIZATION dbo
FROM 0x4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C010300881F94540000000000000000E00002210B010B000010000000060000000000004E2F0000002000000040000000000010002000000002000004000000000000000400000000000000008000000002000000000000030040850000100000100000000010000010000000000000100000000000000000000000FC2E00004F00000000400000C802000000000000000000000000000000000000006000000C000000C42D00001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E74657874000000540F0000002000000010000000020000000000000000000000000000200000602E72737263000000C8020000004000000004000000120000000000000000000000000000400000402E72656C6F6300000C0000000060000000020000001600000000000000000000000000004000004200000000000000000000000000000000302F00000000000048000000020005009C210000280C000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B300300CB00000001000011280700000A730800000A730900000A0A730A00000A0B071F0A6F0B00000A07026F0C00000A07166F0D00000A07036F0E00000A07176F0F00000A076F1000000A731100000A0C086F1200000A086F1300000A0D0972010000706F1400000A096F1500000A13062B321106166F1600000A13041106176F1700000A13050611056F1800000A2D1406110511046F1900000A066F1A00000A6A042E0911066F1B00000A2DC5DE0C11062C0711066F1C00000ADCDE0A092C06096F1C00000ADCDE0A082C06086F1C00000ADC062A0001280000020066003FA5000C000000000200530060B3000A000000000200460079BF000A00000000133002001A0000000200001102A50500001B0A031200281D00000A54041200281E00000A572A1E02281F00000A2A3A02281F00000A02037D2000000A2A3A027B2000000A04036F2100000A2A42534A4201000100000000000C00000076322E302E35303732370000000005006C000000EC020000237E000058030000FC03000023537472696E67730000000054070000300200002355530084090000100000002347554944000000940900009402000023426C6F620000000000000002000001571702080906000000FA25330016000001000000160000000300000001000000050000000900000001000000210000000600000002000000060000000100000003000000010000000100000000000A0001000000000006005700500006007B00600006009A0087000A000901EE0006005A013B0106008C0179011B00A00100000600CF01AF010600EF01AF010A000D02EE000600220260000E00390260000A0062024C020A00E702D4020A0016034C020A002403D4020A0036034C020A004F03D4020A0069034C020A008503D4020600C40350000600D80360000000000001000000000001000100010010002000000005000100010003011000350000000500010004002100C60026005020000000009600A600110001005021000000009600B800190004007621000000008618C000220007007E21000000008618C0002E0007008D2100000000E601CF003800080000000100D700000002001B0100000300280100000100300102000200340102000300670100000100C600000001006E01000002007301030006002100C00022002900C00022003100C00053004100C00059004900C00022005100C000220014002D02E1011C00C0002E002400C0002E006900C000220069007D02590069009002F70169009F02FC016900AA02F7016900BD02FC017100010301027900C000F70181003103220079004103050291005903F701890077030A02A10092030F02A1009B0314022400A50319022400B1031F022400B5032702A100BF032B02A900D00322002C00E70349022C00F1034E020900C00022003400C60026000C00CF003800200033005E0024000B0040002E001B0063022E0023006C022E002B00750244000B0040002F0253020A00DB01EA01F00142025C02048000000000000000000000000000000000A600000002000000000000000000000001004700000000000200000000000000000000000100E200000000000200000000000000000000000100500000000000030002000000000006005E000000003C4D6F64756C653E00466C6F7744697374696E63744F726465722E646C6C0055736572446566696E656446756E6374696F6E730052657665727365436F6D70617265726031006D73636F726C69620053797374656D004F626A65637400540053797374656D2E436F6C6C656374696F6E732E47656E657269630049436F6D706172657260310053797374656D2E436F6C6C656374696F6E730049456E756D657261626C6500466C6F7744697374696E63744F726465720046696C6C526F77002E63746F72006F726967696E616C00436F6D70617265005365727665724E616D650053797374656D2E44617461004D6963726F736F66742E53716C5365727665722E5365727665720053716C46616365744174747269627574650044617461626173654E616D65004D6178526F7773006F626A004375737449440053797374656D2E52756E74696D652E496E7465726F705365727669636573004F757441747472696275746500416D6F756E74006C6566740072696768740053797374656D2E446961676E6F73746963730044656275676761626C6541747472696275746500446562756767696E674D6F6465730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300436F6D70696C6174696F6E52656C61786174696F6E734174747269627574650052756E74696D65436F6D7061746962696C6974794174747269627574650053716C46756E6374696F6E41747472696275746500436F6D70617265726031006765745F44656661756C7400536F7274656444696374696F6E61727960320053797374656D2E446174612E53716C436C69656E740053716C436F6E6E656374696F6E537472696E674275696C646572007365745F436F6E6E65637454696D656F7574007365745F44617461536F75726365007365745F456E6C697374007365745F496E697469616C436174616C6F67007365745F496E746567726174656453656375726974790053797374656D2E446174612E436F6D6D6F6E004462436F6E6E656374696F6E537472696E674275696C646572006765745F436F6E6E656374696F6E537472696E670053716C436F6E6E656374696F6E004462436F6E6E656374696F6E004F70656E0053716C436F6D6D616E6400437265617465436F6D6D616E64004462436F6D6D616E64007365745F436F6D6D616E64546578740053716C4461746152656164657200457865637574655265616465720044624461746152656164657200476574496E74333200476574446F75626C6500436F6E7461696E734B657900416464006765745F436F756E7400526561640049446973706F7361626C6500446973706F7365004B657956616C7565506169726032006765745F56616C7565006765745F4B65790000000000822D0D000A0020002000200020002000200020002000200020002000200020002000200020002000200020002000530045004C004500430054000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020004F002E004300750073007400490044002C0020000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020004F002E0041006D006F0075006E0074000D000A0020002000200020002000200020002000200020002000200020002000200020002000200020002000460052004F004D002000640062006F002E004F007200640065007200730020004100530020004F000D000A00200020002000200020002000200020002000200020002000200020002000200020002000200020005700480045005200450020000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020004F002E00530074006F00720065004900440020003D002000310020000D000A00200020002000200020002000200020002000200020002000200020002000200020002000200020004F00520044004500520020004200590020000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200020002000200020004F002E0041006D006F0075006E00740020004400450053004300008E8B082F3050554B858E01B56306C38B0008B77A5C561934E08906151209011300070003120D0E0E0A080003011C1008100D03200001070615120901130009200101151209011300072002081300130012010001005408074D617853697A658000000005200101111D0420010108817B010004005455794D6963726F736F66742E53716C5365727665722E5365727665722E446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D322E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038390A446174614163636573730100000054557F4D6963726F736F66742E53716C5365727665722E5365727665722E53797374656D446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D322E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038391053797374656D4461746141636365737300000000540E1146696C6C526F774D6574686F644E616D650746696C6C526F77540E0F5461626C65446566696E6974696F6E2643757374494420696E7465676572204E554C4C2C20416D6F756E7420666C6F6174204E554C4C0515122D010D08000015122D0113000515120C010D06151231020D08042001010E04200101020320000E0420001245042000124D04200108080420010D0805200102130007200201130013010320000803200002120707151231020D081235123D1245080D124D06151159020D0804200013010420001300080701151159020D080615120C0113000801000200000000000801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F77730100000000881F945400000000020000001C010000E02D0000E00F00005253445388411786AC332241BCB71A9315A6D3DD07000000633A5C55736572735C5061756C2057686974655C446F63756D656E74735C56697375616C2053747564696F20323031335C50726F6A656374735C466C6F7744697374696E63744F726465725C466C6F7744697374696E63744F726465725C6F626A5C52656C656173655C466C6F7744697374696E63744F726465722E70646200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000242F000000000000000000003E2F0000002000000000000000000000000000000000000000000000302F0000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF250020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001001000000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000584000006C02000000000000000000006C0234000000560053005F00560045005200530049004F004E005F0049004E0046004F0000000000BD04EFFE00000100000000000000000000000000000000003F000000000000000400000002000000000000000000000000000000440000000100560061007200460069006C00650049006E0066006F00000000002400040000005400720061006E0073006C006100740069006F006E00000000000000B004CC010000010053007400720069006E006700460069006C00650049006E0066006F000000A801000001003000300030003000300034006200300000002C0002000100460069006C0065004400650073006300720069007000740069006F006E000000000020000000300008000100460069006C006500560065007200730069006F006E000000000030002E0030002E0030002E00300000004C001600010049006E007400650072006E0061006C004E0061006D006500000046006C006F007700440069007300740069006E00630074004F0072006400650072002E0064006C006C0000002800020001004C006500670061006C0043006F0070007900720069006700680074000000200000005400160001004F0072006900670069006E0061006C00460069006C0065006E0061006D006500000046006C006F007700440069007300740069006E00630074004F0072006400650072002E0064006C006C000000340008000100500072006F006400750063007400560065007200730069006F006E00000030002E0030002E0030002E003000000038000800010041007300730065006D0062006C0079002000560065007200730069006F006E00000030002E0030002E0030002E003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000C000000503F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO
-- The CLR TVF with order guarantee
CREATE FUNCTION dbo.FlowDistinctOrder 
(
    @ServerName nvarchar(128), 
    @DatabaseName nvarchar(128), 
    @MaxRows bigint
)
RETURNS TABLE 
(
    CustID integer NULL, 
    Amount float NULL
)
ORDER (Amount DESC)
AS EXTERNAL NAME FlowDistinctOrder.UserDefinedFunctions.FlowDistinctOrder;

ตารางทดสอบและข้อมูลตัวอย่างจากคำถาม:

-- Test table
CREATE TABLE dbo.Orders
(  
    OrderID    integer  NOT NULL IDENTITY(1,1),
    CustID     integer  NOT NULL,
    StoreID    integer  NOT NULL,
    Amount     float    NOT NULL
);
GO
-- Sample data
WITH  
    Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows  
    Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows 
    Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows 
    Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows 
    Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows 
    FinalCte AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Cte5)
INSERT dbo.Orders 
    (CustID, StoreID, Amount)
SELECT 
    CustID  = Number / 10,
    StoreID = Number % 4,
    Amount  = 1000 * RAND(Number)
FROM FinalCte
WHERE 
    Number <= 1000000;
GO
-- Index
CREATE CLUSTERED INDEX IX 
ON dbo.Orders 
    (StoreID ASC, Amount DESC, CustID ASC);

ฟังก์ชั่นการทดสอบ:

-- Test the function
-- Run several times to ensure connection is cached
-- and CLR code fully compiled
DECLARE @Start datetime2 = SYSUTCDATETIME();

SELECT TOP (500) 
    FDO.CustID
FROM dbo.FlowDistinctOrder
(
    @@SERVERNAME,   -- For external connection
    DB_NAME(),      -- For external connection
    500             -- Number of rows to return
) AS FDO 
ORDER BY 
    FDO.Amount DESC;

SELECT DATEDIFF(MILLISECOND, @Start, SYSUTCDATETIME());

แผนการดำเนินการ (บันทึกการตรวจสอบความถูกต้องของการORDERรับประกัน):

แผนการดำเนินการฟังก์ชัน CLR

บนแล็ปท็อปของฉันนี่ใช้งานได้ใน 80-100ms สิ่งนี้อยู่ใกล้เร็วที่สุดเท่าที่ T-SQL เขียนใหม่ข้างต้น แต่ควรแสดงเสถียรภาพของประสิทธิภาพที่ดีเมื่อเผชิญกับการกระจายข้อมูลที่แตกต่างกัน

รหัสแหล่งที่มา:

using Microsoft.SqlServer.Server;
using System.Collections;
using System.Collections.Generic;
using System.Data.SqlClient;

public partial class UserDefinedFunctions
{
    private sealed class ReverseComparer<T> : IComparer<T>
    {
        private readonly IComparer<T> original;

        public ReverseComparer(IComparer<T> original)
        {
            this.original = original;
        }

        public int Compare(T left, T right)
        {
            return original.Compare(right, left);
        }
    }

    [SqlFunction
        (
        DataAccess = DataAccessKind.Read,
        SystemDataAccess = SystemDataAccessKind.None,
        FillRowMethodName = "FillRow",
        TableDefinition = "CustID integer NULL, Amount float NULL"
        )
    ]
    public static IEnumerable FlowDistinctOrder
        (
        [SqlFacet (MaxSize=128)]string ServerName, 
        [SqlFacet (MaxSize=128)]string DatabaseName,
        long MaxRows
        )
    {
        var list = new SortedDictionary<double, int>
            (new ReverseComparer<double>(Comparer<double>.Default));

        var csb = new SqlConnectionStringBuilder();
        csb.ConnectTimeout = 10;
        csb.DataSource = ServerName;
        csb.Enlist = false;
        csb.InitialCatalog = DatabaseName;
        csb.IntegratedSecurity = true;

        using (var conn = new SqlConnection(csb.ConnectionString))
        {
            conn.Open();
            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText =
                    @"
                    SELECT
                        O.CustID, 
                        O.Amount
                    FROM dbo.Orders AS O
                    WHERE 
                        O.StoreID = 1 
                    ORDER BY 
                        O.Amount DESC";

                int custid;
                double amount;

                using (var rdr = cmd.ExecuteReader())
                {
                    while (rdr.Read())
                    {
                        custid = rdr.GetInt32(0);
                        amount = rdr.GetDouble(1);

                        if (!list.ContainsKey(amount))
                        {
                            list.Add(amount, custid);
                            if (list.Count == MaxRows)
                            {
                                break;
                            }
                        }
                    }
                }
            }
        }
        return list;
    }

    public static void FillRow(object obj, out int CustID, out double Amount)
    {
        var v = (KeyValuePair<double, int>)obj;
        CustID = v.Value;
        Amount = v.Key;
    }
}

6

หากไม่มีORDER BYหลายสิ่งหลายอย่างก็ผิดพลาดได้ คุณได้ยกเว้นปัญหาที่เป็นไปได้ทั้งหมดที่ฉันสามารถนึกถึงได้ แต่นั่นไม่ได้หมายความว่าไม่มีปัญหาและจะไม่มีปัญหาในการเปิดตัวในอนาคต

สิ่งนี้น่าจะใช้ได้:

ดึงกลุ่ม 500 แถวจากตารางในลูปและหยุดเมื่อคุณมี ID ลูกค้าที่แตกต่างกัน 500 รายการ แบบสอบถามดึงข้อมูลอาจมีลักษณะเช่นนี้:

select TOP (500) Amount, CustID
into #fetchedOrders
from Orders
where StoreID = 1234 and Amount <= @lastAmountFetched
order by Amount DESC

สิ่งนี้จะทำการสแกนช่วงที่สั่งซื้อบนดัชนี เพรดิเคตAmount <= @lastAmountFetchedจะอยู่ที่นั่นเพื่อดึงระเบียนเพิ่มขึ้น แบบสอบถามแต่ละรายการจะสัมผัสได้ 500 รายการเท่านั้น นั่นหมายความว่ามันเป็น O (1) มันไม่ได้มีราคาแพงมากขึ้นถ้าคุณเข้าสู่ดัชนี

คุณต้องรักษาตัวแปร@lastAmountFetchedเพื่อลดค่าที่น้อยที่สุดที่คุณดึงข้อมูลในคำสั่งนั้น

วิธีนี้คุณจะสแกนดัชนีแบบเพิ่มเติมตามลำดับ คุณจะอ่านได้มากที่สุด (500 - 1) แถวมากกว่าจำนวนเงินที่เหมาะสม

นี่จะเร็วกว่าการรวมยอดสั่งซื้อ 100,000 รายการหรือมากกว่านั้นสำหรับร้านค้าใดร้านหนึ่งโดยเฉพาะ อาจจำเป็นต้องทำซ้ำเพียงไม่กี่แถวละ 500 แถว

โดยพื้นฐานแล้วนี่คือโอเปอเรเตอร์ที่กำหนดรหัสเองด้วยตนเอง

หรือใช้เคอร์เซอร์เพื่อดึงแถวให้น้อยที่สุด สิ่งนี้จะช้ากว่านี้มากเนื่องจากการเรียกใช้แบบสอบถามแบบแถวเดียว 500 ครั้งส่วนใหญ่มักจะช้ากว่าการเรียกใช้ชุดข้อมูล 500 แถว

หรือมิฉะนั้นเพียงสอบถามทุกแถวโดยไม่ต้องDISTINCTสั่งวิธีและทำให้แอปพลิเคชันไคลเอนต์ยุติการค้นหาเมื่อมีการส่งคืนแถวมากพอ (โดยใช้SqlCommand.Cancel)


1
นี่กำลังขาดรายละเอียดที่สำคัญ - คุณจะมั่นใจได้อย่างไรว่า#fetchedOrdersไม่มีลูกค้าที่เราเคยเห็นมาแล้ว? สันนิษฐานนี้เกี่ยวข้องกับการแสวงหาดัชนีในตาราง temp ซึ่งไม่มากสิ่งเดียวกับ "การไหลที่แตกต่าง" และไม่ได้รับมีราคาแพงกว่าแถวที่เราเคยเห็น (แม้ว่ามันจะยังคงชนะการแก้ปัญหา B แต่ในกรณีที่เลวร้ายที่สุด ต้องสแกนแถวทั้งหมดเนื่องจากมีลูกค้าเพียงรายเดียวเท่านั้นซึ่ง A และ B จะทำงานเหมือนกัน)

2
@JeroenMostert - IGNORE_DUP_KEYสามารถทำเช่นนั้นได้
Martin Smith

@usr: ขอบคุณสำหรับสิ่งนี้ ฉันเขียนโค้ดโดยใช้ IGNORE_DUP_KEY & วิ่งตัวเลข & มีเวลา cpu = 31ms, เวลาที่ผ่านไป = 27ms แม้ว่าจะเร็วกว่าโซลูชัน B แต่ก็ไม่มีที่ไหนใกล้โซลูชัน A (cpu = 0, ms = 1) ซึ่งจำเป็นต้องมีเพื่อจุดประสงค์ของฉัน เมื่อคุณพูดว่า "คุณได้ยกเว้นปัญหาที่เป็นไปได้ทั้งหมดที่ฉันสามารถคิดได้" ฉันสงสัยว่าฉันได้ยกเว้นปัญหาทั้งหมดที่ทุกคนสามารถคิดได้แล้ว สิ่งที่น่าผิดหวังก็คือฉันสามารถจินตนาการได้ว่า SQL ต้องทำอย่างไรเพื่อให้ได้ perf ของ A ฉันแค่ไม่รู้วิธีบอกโดยใช้ ORDER BY
JohnnyM
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.