Nedávno mě můj přítel Erland Sommarskog seznámil s novou ostrovní výzvou. Je založeno na dotazu z veřejného databázového fóra. Samotná výzva není složitá na zvládnutí při použití dobře známých technik, které primárně využívají okenní funkce. Tyto techniky však vyžadují explicitní třídění navzdory přítomnosti podpůrného indexu. To ovlivňuje škálovatelnost a dobu odezvy řešení. S láskou k výzvám jsem se rozhodl najít řešení, jak minimalizovat počet explicitních operátorů řazení v plánu, nebo ještě lépe, eliminovat jejich potřebu úplně. A našel jsem takové řešení.
Začnu tím, že představím obecnou formu výzvy. Poté ukážu dvě řešení založená na známých technikách, po nichž bude následovat nové řešení. Nakonec porovnám výkon různých řešení.
Doporučuji, abyste se pokusili najít řešení před implementací mého.
Výzva
Uvedu zobecněnou formu původní výzvy Erlandových ostrovů.
Pomocí následujícího kódu vytvořte tabulku nazvanou T1 a naplňte ji malou sadou ukázkových dat:
SET NOCOUNT ON; USE tempdb; DROP TABLE IF EXISTS dbo.T1 CREATE TABLE dbo.T1( grp VARCHAR(10) NOT NULL, ord INT NOT NULL, val VARCHAR(10) NOT NULL, CONSTRAINT PK_T1 PRIMARY KEY(grp, ord));GO INSERT INTO T1(grp, ord, val) VALUES ('Skupina A', 1002, 'Y'), ('Skupina A', 1003, 'Y'), ('Skupina A', 1005, 'Y'), (' Skupina A', 1007, 'N'), ('Skupina A', 1011, 'N'), ('Skupina A', 1013, 'N'), ('Skupina A', 1017, 'Y'), ('Skupina A', 1019, 'Y'), ('Skupina A', 1023, 'N'), ('Skupina A', 1029, 'N'), ('Skupina B', 1001, 'X' ), ('Skupina B', 1002, 'X'), ('Skupina B', 1003, 'Z'), ('Skupina B', 1005, 'Z'), ('Skupina B', 1008, ' Z'), ('Skupina B', 1013, 'Z'), ('Skupina B', 1021, 'Y'), ('Skupina B', 1034, 'Y');
Výzva je následující:
Za předpokladu rozdělení na základě sloupce grp a uspořádání na základě sloupce ord vypočítejte sekvenční čísla řádků začínající 1 v každé po sobě jdoucí skupině řádků se stejnou hodnotou ve sloupci val. Následuje požadovaný výsledek pro danou malou sadu vzorových dat:
grp ord val seqno-------- ----- ---- ------Skupina A 1002 Y 1 Skupina A 1003 Y 2 Skupina A 1005 Y 3 Skupina A 1007 N 1 Skupina A 1011 N 2group A 1013 N 3group A 1017 y 1 Group A 1019 y 2group A 1023 N 1 Group A 1029 N 2group B 1001 x 1 Group B 1002 x 2group B 1003 Z 1 GROUP B 1005 Z 2 GROUP B 1008 Z 3 GROUP B 1013 Z 4GROUP B 1021 y 1 GROUP 1 GROUP B 1021 y 1 Group B 1034 Y 2
Všimněte si definice omezení primárního klíče založeného na složeném klíči (grp, ord), jehož výsledkem je seskupený index založený na stejném klíči. Tento index může potenciálně podporovat funkce okna rozdělené podle grp a seřazené podle ord.
Chcete-li otestovat výkon svého řešení, budete muset tabulku naplnit většími objemy ukázkových dat. Pomocí následujícího kódu vytvořte pomocnou funkci GetNums:
CREATE FUNCTION dbo.GetNums(@low AS BIGINT =1, @high AS BIGINT) VRÁTÍ TABLEASRETURN S L0 AS ( SELECT 1 AS c FROM (VALUES(1),(1),(1),(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) AS D (c) ), L1 AS ( VYBERTE 1 JAKO c Z L0 JAKO CŘÍŽOVÉ PŘIPOJENÍ L0 JAKO B ), L2 AS ( VYBERTE 1 JAKO c Z L1 JAKO KŘÍŽOVÉ SPOJE L1 JAKO B ), L3 JAKO ( VYBERTE 1 JAKO c Z L2 AS A CROSS JOIN L2 AS B ), Nums AS ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rownum FROM L3 ) SELECT TOP (@high - @low + 1) rownum AS rn, @high + 1 - rownum AS op, @low - 1 + rownum AS n FROM Nums ORDER BY rownum;GO
Použijte následující kód k naplnění T1, po změně parametrů představujících počet skupin, počet řádků na skupinu a počet odlišných hodnot, jak si přejete:
DECLARE @numgroups AS INT =1000, @rowspergroup AS INT =10000, -- zde testujte s 1000 až 10000 @uniquevalues AS INT =5; ALTER TABLE dbo.T1 DROP CONSTRAINT PK_T1; TRUNCATE TABLE dbo.T1; INSERT INTO dbo.T1 WITH(TABLOCK) (grp, ord, val) SELECT CAST(G.n AS VARCHAR(10)) AS grp, CAST(R.n AS INT) AS ord, CAST(ABS(CHECKSUM(NEWID())) % @uniquevalues + 1 AS VARCHAR(10)) AS val FROM dbo.GetNums(1, @numgroups) AS G CROSS JOIN dbo.GetNums(1, @rowspergroup) AS R; ALTER TABLE dbo.T1 ADD CONSTRAINT PK_T1 PRIMÁRNÍ KLÍČ CLUSTERED(grp, ord);
Ve svých výkonnostních testech jsem naplnil tabulku 1 000 skupinami, mezi 1 000 a 10 000 řádky na skupinu (takže 1 až 10 milionů řádků) a 5 odlišnými hodnotami. Použil jsem SELECT INTO
příkaz zapsat výsledek do dočasné tabulky.
Můj testovací stroj má čtyři logické procesory se systémem SQL Server 2019 Enterprise.
Předpokládám, že používáte prostředí navržené pro podporu dávkového režimu v úložišti řádků buď přímo, např. pomocí SQL Server 2019 Enterprise edition jako já, nebo nepřímo vytvořením fiktivního indexu columnstore v tabulce.
Pamatujte si, že body navíc, pokud se vám podaří přijít s efektivním řešením bez explicitního řazení v plánu. Hodně štěstí!
Je při optimalizaci funkcí okna potřeba operátor řazení?
Než se budu věnovat řešením, trochu pozadí optimalizace, takže to, co později uvidíte v plánech dotazů, bude dávat větší smysl.
Nejběžnější techniky pro řešení ostrovních úkolů, jako je ten náš, zahrnují použití určité kombinace agregačních a/nebo hodnotících okenních funkcí. SQL Server může zpracovat takové funkce okna buď pomocí řady starších operátorů v režimu řádků (Segment, Sekvenční projekt, Segment, Spool oken, Stream Aggregate) nebo pomocí novějšího a obvykle efektivnějšího operátora Window Aggregate v dávkovém režimu.
V obou případech musí operátoři, kteří provádějí výpočet funkce okna, zpracovat data objednaná prvky pro rozdělení a uspořádání oken. Pokud nemáte podpůrný index, SQL Server bude přirozeně muset do plánu zavést operátor řazení. Pokud máte například ve svém řešení více funkcí oken s více než jednou jedinečnou kombinací rozdělení a řazení, musíte mít v plánu explicitní řazení. Ale co když máte pouze jednu jedinečnou kombinaci dělení a řazení a podpůrný index?
Starší metoda režimu řádků se může spoléhat na předem objednaná data ingestovaná z indexu bez potřeby explicitního operátoru řazení v sériovém i paralelním režimu. Novější operátor dávkového režimu odstraňuje velkou část neefektivnosti optimalizace staršího režimu řádků a má přirozené výhody zpracování v dávkovém režimu. Jeho současná paralelní implementace však vyžaduje operátor paralelního řazení v dávkovém režimu, i když je přítomen podpůrný index. Pouze jeho sériová implementace se může spolehnout na pořadí indexů bez operátoru Sort. To je vše, co říci, když optimalizátor potřebuje zvolit strategii pro práci s vaší funkcí okna, za předpokladu, že máte podpůrný index, bude to obecně jedna z následujících čtyř možností:
- Řádkový režim, sériový, žádné řazení
- Řádkový režim, paralelní, žádné řazení
- Dávkový režim, sériový, žádné třídění
- Dávkový režim, paralelní, třídění
Bez ohledu na to, která z nich povede k nejnižším nákladům plánu, bude vybrána, za předpokladu, že jsou při zvažování splněny samozřejmě předpoklady pro paralelismus a dávkový režim. Normálně, aby optimalizátor ospravedlnil paralelní plán, musí výhody paralelismu převážit nad prací navíc, jako je synchronizace vláken. U možnosti 4 výše musí výhody paralelismu převážit obvyklou práci navíc spojenou s paralelismem a navíc další třídění.
Při experimentování s různými řešeními naší výzvy jsem měl případy, kdy optimalizátor zvolil výše uvedenou možnost 2. Vybral si to navzdory skutečnosti, že metoda řádkového režimu zahrnuje několik neefektivností, protože výhody v paralelismu a žádné třídění vedly k plánu s nižšími náklady než alternativy. V některých z těchto případů vedlo vynucení sériového plánu s nápovědou k možnosti 3 výše a k lepšímu výkonu.
S ohledem na toto pozadí se podívejme na řešení. Začnu dvěma řešeními, která se spoléhají na známé techniky pro ostrovní úkoly, které nemohou uniknout explicitnímu řazení v plánu.
Řešení založené na známé technice 1
Následuje první řešení naší výzvy, které je založeno na technice, která je již nějakou dobu známá:
WITH C AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) - ROW_NUMBER() OVER(PARTITION BY grp, val ORDER BY ord) AS island FROM dbo.T1)SELECT grp, ord, val , ROW_NUMBER() OVER(PARTITION BY grp, val, island ORDER BY ord) AS seqnoFROM C;
Budu to označovat jako známé řešení 1.
CTE nazývané C je založeno na dotazu, který počítá dvě čísla řádků. První (budu ho označovat jako rn1) je rozdělen podle grp a uspořádán podle ord. Druhý (budu ho označovat jako rn2) je rozdělen podle grp a val a uspořádán podle ord. Protože rn1 má mezery mezi různými skupinami stejné hodnoty a rn2 nikoli, rozdíl mezi rn1 a rn2 (sloupec nazývaný ostrov) je jedinečná konstantní hodnota pro všechny řádky se stejnými hodnotami grp a val. Následuje výsledek vnitřního dotazu, včetně výsledků dvouřádkových číselných výpočtů, které nejsou vráceny jako samostatné sloupce:
grp ord val rn1 rn2 island-------- ----- ---- ---- ---- -------Skupina A 1002 Y 1 1 0 Skupina A 1003 0 A 2 2 0 Skupina A 1005 Y 3 3 0 Skupina A 1007 N 4 1 3 Skupina A 1011 N 5 2 3 Skupina A 1013 N 6 3 3 Skupina A 1017 Y 7 4 3 Skupina A 10519 A 10519 Y1 5 5 Skupina B 1001 X 1 1 0 Skupina B 1002 X 2 2 0 Skupina B 1003 Z 3 1 2 Skupina B 1005 Z 4 2 2 Skupina B 1008 Z 5 3 2 Skupina B 1042 1 Z 6 06
Vnějšímu dotazu zbývá vypočítat výsledný sloupec seqno pomocí funkce ROW_NUMBER, rozdělený podle grp, val a island a seřazený podle ord, čímž se vygeneruje požadovaný výsledek.
Všimněte si, že můžete získat stejnou hodnotu ostrova pro různé hodnoty hodnot v rámci stejného oddílu, jako je tomu ve výstupu výše. To je důvod, proč je důležité používat grp, val a island jako rozdělovací prvky oken a ne samotné grp a island.
Podobně, pokud máte co do činění s úlohou ostrovů, která vyžaduje seskupení dat podle ostrova a výpočet skupinových agregací, seskupili byste řádky podle grp, val a island. Ale to není případ naší výzvy. Zde jste měli za úkol pouze vypočítat čísla řádků nezávisle pro každý ostrov.
Obrázek 1 má výchozí plán, který jsem pro toto řešení získal na svém počítači po naplnění tabulky 10 miliony řádků.
Obrázek 1:Paralelní plán pro známé řešení 1
Výpočet rn1 se může spoléhat na pořadí indexu. Optimalizátor tedy zvolil strategii režimu bez řazení + paralelní řádky (první operátory Segment a Sekvenční projekt v plánu). Vzhledem k tomu, že výpočty rn2 i seqno používají své vlastní jedinečné kombinace prvků rozdělení a řazení, explicitní třídění je nevyhnutelné pro ty, bez ohledu na použitou strategii. Optimalizátor tedy zvolil strategii řazení + paralelní dávkový režim pro oba. Tento plán zahrnuje dva explicitní operátory řazení.
V mém testu výkonu trvalo tomuto řešení 3,68 sekundy, než bylo dokončeno u 1 milionu řádků a 43,1 sekundy u 10 milionů řádků.
Jak již bylo zmíněno, všechna řešení jsem testoval také vynucením sériového plánu (s nápovědou MAXDOP 1). Sériový plán tohoto řešení je znázorněn na obrázku 1.
Obrázek 2:Sériový plán pro známé řešení 1
Jak se očekávalo, tentokrát také výpočet rn1 používá strategii dávkového režimu bez předchozího operátoru řazení, ale plán má stále dva operátory řazení pro následující výpočty čísel řádků. Sériový plán fungoval hůře než paralelní plán na mém počítači se všemi vstupními velikostmi, které jsem testoval, dokončení zabralo 4,54 sekundy s 1 milionem řádků a 61,5 sekundy s 10 miliony řádků.
Řešení založené na známé technice 2
Druhé řešení, které představím, je založeno na skvělé technice pro detekci ostrovů, kterou jsem se naučil od Paula Whitea před několika lety. Následuje úplný kód řešení založený na této technice:
WITH C1 AS( SELECT *, CASE WHEN val =LAG(val) OVER(PARTITION BY grp ORDER BY ord) THEN 0 ELSE 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(isstart) OVER(ODDĚLENÍ PODLE SKUPINY OBJEDNÁVKA PODLE ŘÁDKŮ BEZ ODPOVĚDNOSTI PŘEDCHOZÍM) JAKO ostrov OD C1)VYBERTE grp, ord, val, ŘÁDEK_ČÍSLO() PŘED(ODDĚLENÍ PODLE sk., ostrov ŘADÍ PODLE ord) AS seqnoFROM C2;
Toto řešení budu označovat jako známé řešení 2.
Dotaz definující výpočet CTE C1 používá výraz CASE a funkci okna LAG (rozdělené podle grp a seřazené podle ord) k výpočtu výsledného sloupce nazvaného isstart. Tento sloupec má hodnotu 0, když je aktuální hodnota stejná jako předchozí, a 1 jinak. Jinými slovy, je to 1, když je řádek první v ostrůvku, a 0 jinak.
Následuje výstup dotazu definujícího C1:
grp ord val isstart-------- ----- ---- --------- Skupina A 1002 Y 1 Skupina A 1003 Y 0 Skupina A 1005 Y 0 Skupina A 1007 N 1 Skupina A 1011 n 0group a 1013 n 0group a 1017 y 1 group a 1019 y 0 group a 1023 n 1 group a 1029 n 0group B 1001 x 1group B 1002 x 0group B 1003 Z 1group B 1005 Z 0group B 1008 Z 0group B 1013 Z 0group B 1021 y 1021 y 1021 y 1021 y 1021 y 1021 y 1021 y 1021 y 1021 y 1021 y 1Skupina B 1034 Y 0
Kouzlo, pokud jde o detekci ostrovů, se děje v CTE C2. Dotaz, který jej definuje, vypočítá identifikátor ostrova pomocí funkce okna SUM (také rozdělené podle grp a seřazené podle ord) aplikované na sloupec isstart. Výsledný sloupec s identifikátorem ostrova se nazývá ostrov. V rámci každého oddílu získáte 1 za první ostrov, 2 za druhý ostrov a tak dále. Kombinace sloupců grp a island je tedy identifikátor ostrova, který můžete použít v úkolech týkajících se ostrovů, které zahrnují seskupování podle ostrova, je-li to relevantní.
Následuje výstup dotazu definujícího C2:
grp ord val isstart island-------- ----- ---- -------- -------Skupina A 1002 Y 1 1 Skupina A 1003 Y 0 1 Skupina A 1005 Y 0 1 Skupina A 1007 N 1 2 Skupina A 1011 N 0 2 Skupina A 1013 N 0 2 Skupina A 1017 Y 1 3 Skupina A 1019 Y 0 3 Skupina A 10423 N 10423 N 09 1 Skupina B 1003 Z 1 2 Skupina B 1005 Z 0 2 Skupina B 1008 Z 0 2 Skupina B 1013 Z 0 2 Skupina B 1021 Y 1 3 Skupina B 1034 Y 0 3
Nakonec vnější dotaz vypočítá požadovaný výsledný sloupec seqno s funkcí ROW_NUMBER, rozdělený podle grp a island a seřazený podle ord. Všimněte si, že tato kombinace prvků rozdělení a řazení se liší od té, kterou používají předchozí funkce okna. Zatímco výpočet prvních dvou funkcí okna se může potenciálně spoléhat na pořadí indexu, poslední nikoli.
Obrázek 3 má výchozí plán, který jsem získal pro toto řešení.
Obrázek 3:Paralelní plán pro známé řešení 2
Jak můžete vidět v plánu, výpočet prvních dvou funkcí okna používá strategii režimu bez řazení + paralelní řádek a výpočet poslední používá strategii režimu řazení + paralelního dávkového režimu.
Doby běhu, které jsem získal pro toto řešení, se pohybovaly od 2,57 sekundy proti 1 milionu řádků do 46,2 sekundy proti 10 milionům řádků.
Při vynucení sériového zpracování jsem dostal plán znázorněný na obrázku 4.
Obrázek 4:Sériový plán pro známé řešení 2
Jak se očekávalo, tentokrát všechny výpočty funkcí okna spoléhají na strategii dávkového režimu. První dva bez předchozího řazení a poslední s. Paralelní i sériový plán zahrnovaly jednoho explicitního operátora řazení. Sériový plán fungoval lépe než paralelní plán na mém počítači se vstupními velikostmi, které jsem testoval. Časy běhu, které jsem získal pro vynucený sériový plán, se pohybovaly od 1,75 sekundy proti 1 milionu řádků do 21,7 sekundy proti 10 milionům řádků.
Řešení založené na nové technice
Když Erland představil tuto výzvu na soukromém fóru, lidé byli skeptičtí k možnosti vyřešit ji pomocí dotazu, který byl optimalizován pomocí plánu bez explicitního třídění. To je vše, co jsem potřeboval slyšet, aby mě motivovalo. Takže, na co jsem přišel:
WITH C1 AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) AS rn, LAG(val) OVER(PARTITION BY grp ORDER BY ord) AS předchozí FROM dbo.T1),C2 AS( SELECT *, MAX(PŘÍPAD, KDYŽ val =prev THEN NULL ELSE rn KONEC) NAD(ODDĚLENÍ PODLE skupiny ŘADA PODLE ŘÁDKŮ POŘADÍ BEZ ODPOVĚDNOSTI PŘEDCHOZÍM) AS firstrn OD C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoFROM C2;Řešení využívá tři funkce okna:LAG, ROW_NUMBER a MAX. Důležité je, že všechny tři jsou založeny na rozdělení grp a pořadí pořadí, které je v souladu s podpůrným pořadím indexových klíčů.
Dotaz definující CTE C1 používá funkci ROW_NUMBER k výpočtu čísel řádků (sloupec rn) a funkci LAG k vrácení předchozí hodnoty hodnoty (předchozí sloupec).
Zde je výstup dotazu definujícího C1:
grp ord val rn prev-------- ----- ---- --- -----Skupina A 1002 Y 1 NULLGroup A 1003 Y 2 YGroup A 1005 Y 3 YGroup A BGro 1005 Z 4 ZSkupina B 1008 Z 5 ZSkupina B 1013 Z 6 ZSkupina B 1021 Y 7 ZSkupina B 1034 Y 8 YVšimněte si, že když jsou hodnoty val a prev stejné, není to první řada na ostrově, jinak ano.
Na základě této logiky používá dotaz definující CTE C2 výraz CASE, který vrací rn, když je řádek první na ostrově, a jinak NULL. Kód pak použije funkci okna MAX na výsledek výrazu CASE a vrátí první rn ostrova (první sloupec).
Zde je výstup dotazu definujícího C2, včetně výstupu výrazu CASE:
grp ord val rn prev CASE firstrn-------- ----- ---- --- ----- ----- --------Skupina A A 1002 Y 1 NULL 1 1Skupina A 1003 Y 2 Y NULL 1 Skupina A 1005 Y 3 Y NULL 1 Skupina A 1007 N 4 Y 4 4 Skupina A 1011 N 5 N NULL 4 Skupina A 06 A 1013 N NULL 4 Skupina A 10773 N 1077 N 1077 N B 8 Y NULL 7Skupina A 1023 N 9 Y 9 9Skupina A 1029 N 10 N NULL 9Skupina B 1001 X 1 NULL 1 1Skupina B 1002 X 2 X NULL 1Skupina B 1003 31 Z 3 X 3 03 03 5 Z NULL 3 Skupina B 1013 Z 6 Z NULL 3 Skupina B 1021 Y 7 Z 7 7 Skupina B 1034 Y 8 Y NULL 7Co zbývá pro vnější dotaz, je vypočítat požadované seqno výsledného sloupce jako rn minus firstrn plus 1.
Obrázek 5 ukazuje výchozí paralelní plán, který jsem pro toto řešení získal při testování pomocí příkazu SELECT INTO, který zapisuje výsledek do dočasné tabulky.
Obrázek 5:Paralelní plán pro nové řešení
V tomto plánu nejsou žádné explicitní operátory řazení. Všechny tři funkce okna se však počítají pomocí strategie režimu bez řazení + paralelního řádku, takže nám chybí výhody dávkového zpracování. Doby běhu, které jsem získal pro toto řešení s paralelním plánem, se pohybovaly od 2,47 sekundy proti 1 milionu řádků a 41,4 proti 10 milionů řádků.
Zde je poměrně vysoká pravděpodobnost, že sériový plán s dávkovým zpracováním bude fungovat výrazně lépe, zvláště když stroj nemá mnoho CPU. Připomeňme, že testuji svá řešení proti počítači se 4 logickými CPU. Obrázek 6 ukazuje plán, který jsem získal pro toto řešení při vynucení sériového zpracování.
Obrázek 6:Sériový plán nového řešení
Všechny tři funkce okna používají strategii dávkového režimu bez řazení + sériového režimu. A výsledky jsou docela působivé. Doba běhu tohoto roztoku se pohybovala od 0,5 sekundy proti 1M řádkům a 5,49 sekundy proti 10M řádkům. Co je na tomto řešení také zajímavé, je, že při testování jako normálního příkazu SELECT (s výsledky zahozenými) na rozdíl od příkazu SELECT INTO zvolil SQL Server ve výchozím nastavení sériový plán. S dalšími dvěma řešeními jsem ve výchozím nastavení získal paralelní plán jak s SELECT, tak s SELECT INTO.
Kompletní výsledky testu výkonu naleznete v další části.
Porovnání výkonu
Zde je kód, který jsem použil k testování těchto tří řešení, samozřejmě bez komentáře MAXDOP 1 k testování sériových plánů:
-- Otestujte známé řešení 1 ZAHOĎTE TABULKU, POKUD EXISTUJE #Výsledek; WITH C AS( SELECT *, ROW_NUMBER() OVER(ODDĚLENÍ PODLE GR ORDER BY ord) - ROW_NUMBER() OVER(PARTITION BY grp, val ORDER BY ord) AS island FROM dbo.T1)SELECT grp, ord, val, ROW_NUMBER( ) OVER(PARTITION BY grp, val, island ORDER BY ord) AS seqnoINTO #ResultFROM C/* OPTION(MAXDOP 1) */; -- odkomentovat pro sériový testGO -- Test známého řešení 2 DROP TABLE IF EXISTS #Result; WITH C1 AS( SELECT *, CASE WHEN val =LAG(val) OVER(PARTITION BY grp ORDER BY ord) THEN 0 ELSE 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(isstart) OVER(PARTITION BY grp ORDER BY ord ŘÁDKY BEZ ODPOVĚDNOSTI PŘEDCHOZÍ) AS ostrov FROM C1)SELECT grp, ord, val, ROW_NUMBER() OVER(PARTITION BY grp, island ORDER BY ord) AS seqnoINTO #ResultFROM C2/* OPTION(MAXDOP 1) */; GO -- Test nové řešení DROP TABLE IF EXISTS #Result; WITH C1 AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) AS rn, LAG(val) OVER(PARTITION BY grp ORDER BY ord) AS předchozí FROM dbo.T1),C2 AS(SELECT *, MAX (PŘÍPAD, KDYŽ val =prev THEN NULL ELSE rn END) OVER (ODDĚLENÍ PODLE SKUPINY OBJEDNÁVKA PODLE ŘÁDKŮ POŘADÍ BEZ ODPOVĚDNOSTI PŘEDCHOZÍM) AS firstrn OD C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoINTO #ResultFROM C2/* OPTION( MAXDOP 1) */;Obrázek 7 ukazuje doby běhu jak paralelního, tak sériového plánu pro všechna řešení s různými vstupními velikostmi.
Obrázek 7:Porovnání výkonu
Nové řešení využívající sériový režim je jasným vítězem. Má skvělý výkon, lineární škálování a okamžitou dobu odezvy.
Závěr
Úkoly na ostrovech jsou v reálném životě zcela běžné. Mnohé z nich zahrnují jak identifikaci ostrovů, tak seskupování dat podle ostrova. Výzva Erland's Islands Challenge, na kterou byl zaměřen tento článek, je o něco unikátnější, protože nezahrnuje seskupování, ale místo toho seřazení řádků každého ostrova pomocí čísel řádků.
Představil jsem dvě řešení založená na známých technikách identifikace ostrovů. Problém s oběma je, že vedou k plánům zahrnujícím explicitní třídění, což negativně ovlivňuje výkon, škálovatelnost a dobu odezvy řešení. Také jsem představil novou techniku, která vyústila v plán bez řazení. Sériový plán pro toto řešení, které používá strategii bez řazení + sériový dávkový režim, má vynikající výkon, lineární škálování a okamžitou dobu odezvy. Je nešťastné, alespoň prozatím nemůžeme mít strategii bez řazení + paralelní dávkový režim pro práci s funkcemi okna.