Nyligen introducerades jag för en ny ö-utmaning av min vän Erland Sommarskog. Den är baserad på en fråga från ett offentligt databasforum. Utmaningen i sig är inte komplicerad att hantera när man använder välkända tekniker, som främst använder fönsterfunktioner. Dessa tekniker kräver dock explicit sortering trots närvaron av ett stödjande index. Detta påverkar skalbarheten och svarstiden för lösningarna. Jag är intresserad av utmaningar och försökte hitta en lösning för att minimera antalet explicita sorteringsoperatörer i planen, eller ännu bättre, eliminera behovet av dessa helt och hållet. Och jag hittade en sådan lösning.
Jag börjar med att presentera en generaliserad form av utmaningen. Jag visar sedan två lösningar baserade på kända tekniker, följt av den nya lösningen. Slutligen ska jag jämföra prestandan för de olika lösningarna.
Jag rekommenderar att du försöker hitta en lösning innan du implementerar min.
Utmaningen
Jag kommer att presentera en generaliserad form av Erlands ursprungliga ö-utmaning.
Använd följande kod för att skapa en tabell som heter T1 och fylla i den med en liten uppsättning exempeldata:
STÄLL IN NOCOUNT PÅ; ANVÄND tempdb; SLIP TABELL OM FINNS dbo.T1 SKAPA TABELL dbo.T1( grp VARCHAR(10) NOT NULL, ord INT NOT NULL, val VARCHAR(10) NOT NULL, BEGRÄNSNING PK_T1 PRIMÄRKEY(grp, ord));GO INSERT INTO dbo. T1(grp, ord, val) VÄRDEN ('Grupp A', 1002, 'Y'), ('Grupp A', 1003, 'Y'), ('Grupp A', 1005, 'Y'), (' Grupp A', 1007, 'N'), ('Grupp A', 1011, 'N'), ('Grupp A', 1013, 'N'), ('Grupp A', 1017, 'Y'), ('Grupp A', 1019, 'Y'), ('Grupp A', 1023, 'N'), ('Grupp A', 1029, 'N'), ('Grupp B', 1001, 'X' ), ('Grupp B', 1002, 'X'), ('Grupp B', 1003, 'Z'), ('Grupp B', 1005, 'Z'), ('Grupp B', 1008, ' Z'), ('Grupp B', 1013, 'Z'), ('Grupp B', 1021, 'Y'), ('Grupp B', 1034, 'Y');
Utmaningen är följande:
Med antagande om partitionering baserat på kolumnen grp och ordning baserat på kolumnen ord, beräkna sekventiella radnummer som börjar med 1 inom varje på varandra följande grupp av rader med samma värde i valkolumnen. Följande är det önskade resultatet för den givna lilla uppsättningen exempeldata:
grp ord val seqno-------- ----- ---- ------Grupp A 1002 Y 1Grupp A 1003 Y 2Grupp A 1005 Y 3Grupp A 1007 N 1Grupp A 1011 N 2Group A 1013 N 3Group A 1017 Y 1Group A 1019 Y 2Group A 1023 N 1Group A 1029 N 2Group B 1001 X 1Group B 1002 X 2Group B 1003 Z 1Group B 1005 Z 2Group B 1008 Z 3Group B 1013 Z 4Group B 1021 Y 1 Group B -GOUP B BOUR 1034 Y 2
Notera definitionen av primärnyckelbegränsningen baserad på den sammansatta nyckeln (grp, ord), vilket resulterar i ett klustrat index baserat på samma nyckel. Detta index kan potentiellt stödja fönsterfunktioner som är partitionerade av grp och ordnade efter ord.
För att testa din lösnings prestanda måste du fylla i tabellen med större volymer exempeldata. Använd följande kod för att skapa hjälpfunktionen GetNums:
SKAPA FUNKTION dbo.GetNums(@low AS BIGINT =1, @high AS BIGINT) RETURER TABLEASRETURN MED L0 AS ( VÄLJ 1 AS c FROM (VÄRDEN(1),(1),(1),(1), (1),(1),(1),(1), (1),(1),(1),(1),(1),(1),(1),(1)) AS D (c) ), L1 AS ( VÄLJ 1 AS c FRÅN L0 SOM EN KORSKOPPLING L0 AS B ), L2 AS ( VÄLJ 1 AS c FRÅN L1 SOM EN KORSJOIN L1 AS B ), L3 AS ( VÄLJ 1 AS c FRÅN L2 SOM EN KORS 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
Använd följande kod för att fylla i T1, efter att ha ändrat parametrarna som representerar antalet grupper, antalet rader per grupp och antalet distinkta värden som du vill:
DECLARE @numgroups AS INT =1000, @rowspergroup AS INT =10000, -- testa med 1000 till 10000 här @uniquevalues AS INT =5; ALTER TABLE dbo.T1 SLÄPP BEGRÄNSNING PK_T1; TRUNCATE TABELL 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())) % @uniquevärden + 1 AS VARCHAR(10)) AS val FRÅN dbo.GetNums(1, @numgroups) AS G CROSS JOIN dbo.GetNums(1, @rowspergroup) AS R; ALTER TABLE dbo.T1 ADD CONSTRAINT PK_T1 PRIMARY KEY CLUSTERED(grp, ord);
I mina prestationstester fyllde jag i tabellen med 1 000 grupper, mellan 1 000 och 10 000 rader per grupp (alltså 1M till 10M rader) och 5 distinkta värden. Jag använde en SELECT INTO
för att skriva resultatet i en temporär tabell.
Min testmaskin har fyra logiska processorer som kör SQL Server 2019 Enterprise.
Jag antar att du använder en miljö som är utformad för att stödja batchläge i radlager antingen direkt, t.ex. genom att använda SQL Server 2019 Enterprise-utgåvan som min, eller indirekt genom att skapa ett dummy kolumnlagerindex på bordet.
Kom ihåg, extrapoäng om du lyckas komma med en effektiv lösning utan explicit sortering i planen. Lycka till!
Behövs en sorteringsoperator för optimering av fönsterfunktioner?
Innan jag tar upp lösningar, lite optimeringsbakgrund så det du kommer att se i frågeplanerna senare kommer att vara mer meningsfullt.
De vanligaste teknikerna för att lösa ö-uppgifter som vår innebär att man använder någon kombination av aggregat- och/eller rangordningsfönsterfunktioner. SQL Server kan bearbeta sådana fönsterfunktioner med antingen en serie äldre radlägesoperatorer (Segment, Sequence Project, Segment, Window Spool, Stream Aggregate) eller den nyare och vanligtvis mer effektiva batch-mode Window Aggregate-operatorn.
I båda fallen måste de operatörer som hanterar fönsterfunktionens beräkning ta in data som beställts av fönsterpartitionerings- och beställningselementen. Om du inte har ett stödjande index måste SQL Server naturligtvis införa en sorteringsoperatör i planen. Till exempel, om du har flera fönsterfunktioner i din lösning med mer än en unik kombination av partitionering och beställning, är du skyldig att ha explicit sortering i planen. Men vad händer om du bara har en unik kombination av partitionering och beställning och ett stödjande index?
Den äldre radlägesmetoden kan förlita sig på förbeställd data som tas in från ett index utan behov av en explicit sorteringsoperator i både seriella och parallella lägen. Den nyare batchlägesoperatören eliminerar mycket av ineffektiviteten i den äldre radlägesoptimeringen och har de inneboende fördelarna med batchlägesbearbetning. Emellertid kräver dess nuvarande parallellimplementering en parallellsorteringsoperator för mellanliggande batchläge även när ett stödjande index finns. Endast dess seriella implementering kan förlita sig på indexordning utan en sorteringsoperator. Detta är allt att säga när optimeraren behöver välja en strategi för att hantera din fönsterfunktion, förutsatt att du har ett stödjande index, kommer det i allmänhet att vara ett av följande fyra alternativ:
- Radläge, seriellt, ingen sortering
- Radläge, parallellt, ingen sortering
- Batchläge, seriellt, ingen sortering
- Satsläge, parallell, sortering
Vilken som än resulterar i den lägsta plankostnaden kommer att väljas, förutsatt att förutsättningarna för parallellitet och batch-läge är uppfyllda när man överväger dessa. Normalt, för att optimeraren ska motivera en parallell plan, måste fördelarna med parallellitet uppväga det extra arbetet som trådsynkronisering. Med alternativ 4 ovan måste fördelarna med parallellitet uppväga det vanliga extraarbetet med parallellitet, plus den extra sorteringen.
När jag experimenterade med olika lösningar på vår utmaning hade jag fall där optimeraren valde alternativ 2 ovan. Man valde det trots att radlägesmetoden innebär några ineffektiviteter eftersom fördelarna med parallellitet och ingen sortering resulterade i en plan med lägre kostnad än alternativen. I vissa av dessa fall resulterade framtvingandet av en serieplan med en ledtråd i alternativ 3 ovan och i bättre prestanda.
Med denna bakgrund i åtanke, låt oss titta på lösningar. Jag börjar med två lösningar som förlitar sig på kända tekniker för öuppgifter som inte kan undgå explicit sortering i planen.
Lösning baserad på känd teknik 1
Följande är den första lösningen på vår utmaning, som bygger på en teknik som har varit känd ett tag:
MED C AS( SELECT *, ROW_NUMBER() ÖVER(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;
Jag hänvisar till det som känd lösning 1.
CTE som kallas C är baserad på en fråga som beräknar två radnummer. Den första (jag kommer att hänvisa till den som rn1) är partitionerad av grp och beställd av ord. Den andra (jag kommer att referera till den som rn2) är partitionerad av grp och val och beställd av ord. Eftersom rn1 har luckor mellan olika grupper av samma val och rn2 inte har, är skillnaden mellan rn1 och rn2 (kolumn som kallas ö) ett unikt konstant värde för alla rader med samma grp- och valvärden. Följande är resultatet av den inre frågan, inklusive resultaten av nummerberäkningarna med två rader, som inte returneras som separata kolumner:
grp ord val rn1 rn2 island-------- ----- ---- ---- ---- -------Grupp A 1002 Y 1 1 0Grupp A 1003 Y 2 2 0Grupp A 1005 Y 3 3 0Grupp A 1007 N 4 1 3Grupp A 1011 N 5 2 3Grupp A 1013 N 6 3 3Grupp A 1017 Y 7 4 3Grupp A 1019 Y 1019 A 1019 Y 0 5 5 5Grupp B 1001 X 1 1 0Grupp B 1002 X 2 2 0Grupp B 1003 Z 3 1 2Grupp B 1005 Z 4 2 2Grupp B 1008 Z 5 3 2Grupp B 1013 Group 1 G 4 G 1 B 1 G 4 Å 6 Å 6 Å 4 G 1
Vad som återstår för den yttre frågan att göra är att beräkna resultatkolumnen seqno med funktionen ROW_NUMBER, partitionerad efter grp, val och island, och ordnad efter ord, vilket genererar det önskade resultatet.
Observera att du kan få samma ö-värde för olika val-värden inom samma partition, som i utgången ovan. Det är därför det är viktigt att använda grp, val och island som fönsterpartitioner och inte bara grp och island.
På liknande sätt, om du har att göra med en ö-uppgift som kräver att du grupperar data efter ön och beräknar gruppaggregat, skulle du gruppera raderna efter grp, val och ö. Men detta är inte fallet med vår utmaning. Här fick du i uppdrag att bara beräkna radnummer oberoende för varje ö.
Figur 1 har standardplanen som jag fick för den här lösningen på min dator efter att ha fyllt tabellen med 10 miljoner rader.
Figur 1:Parallellplan för känd lösning 1
Beräkningen av rn1 kan förlita sig på indexordning. Så, optimeraren valde strategin ingen sortering + parallell radläge för den här (första segment- och sekvensprojektoperatörerna i planen). Eftersom beräkningarna för både rn2 och seqno använder sina egna unika kombinationer av partitionerings- och ordningselement, är explicit sortering oundviklig för dem, oavsett vilken strategi som används. Så, optimeraren valde strategin sort + parallell batchläge för båda. Denna plan involverar två explicita sorteringsoperatorer.
I mitt prestandatest tog det den här lösningen 3,68 sekunder att slutföra mot 1 miljoner rader och 43,1 sekunder mot 10 miljoner rader.
Som nämnts testade jag alla lösningar också genom att tvinga fram en serieplan (med en MAXDOP 1-tips). Serieplanen för denna lösning visas i figur 1.
Figur 2:Serieplan för känd lösning 1
Som väntat använder beräkningen av rn1 även denna gång batchlägesstrategin utan en föregående sorteringsoperator, men planen har fortfarande två sorteringsoperatorer för de efterföljande radnummerberäkningarna. Serieplanen fungerade sämre än den parallella på min maskin med alla inmatningsstorlekar jag testade, och det tog 4,54 sekunder att komplettera med 1M rader och 61,5 sekunder med 10M rader.
Lösning baserad på känd teknik 2
Den andra lösningen jag kommer att presentera är baserad på en briljant teknik för ödetektering som jag lärde mig av Paul White för några år sedan. Följande är den fullständiga lösningskoden baserad på denna teknik:
MED C1 AS( SELECT *, CASE NÄR val =LAG(val) OVER(PARTITION BY GRP ORDER BY ord) THEN 0 ANDES 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(isstart) ÖVER(DELNING EFTER grp ORDNING EFTER ORD RADER OBEGRÄNSAD FÖREGÅENDE) SOM ö FRÅN C1)VÄLJ grp, ord, val, ROW_NUMBER() ÖVER(PARTITION PER grp, ö ORDER BY ORD) SOM sekvensFrån C2;
Jag kommer att referera till den här lösningen som känd lösning 2.
Frågan som definierar CTE C1-beräkningarna använder ett CASE-uttryck och LAG-fönsterfunktionen (partitionerad av grp och ordnad efter ord) för att beräkna en resultatkolumn som kallas isstart. Denna kolumn har värdet 0 när det aktuella värdet är detsamma som det föregående och 1 annars. Med andra ord, det är 1 när raden är den första på en ö och 0 annars.
Följande är resultatet av frågan som definierar C1:
grp ord val isstart-------- ----- ---- --------Grupp A 1002 Y 1Grupp A 1003 Y 0Grupp A 1005 Y 0Grupp A 1007 N 1Grupp A 1011 N 0Group A 1013 N 0Group A 1017 Y 1Group A 1019 Y 0Group A 1023 N 1Group 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 1Grupp B 1034 Y 0
Magin när det gäller ödetektering sker i CTE C2. Frågan som definierar den beräknar en ö-identifierare med hjälp av SUM-fönsterfunktionen (även partitionerad av grp och ordnad efter ord) applicerad på isstart-kolumnen. Resultatkolumnen med ö-identifieraren kallas ö. Inom varje partition får du 1 för den första ön, 2 för den andra ön, och så vidare. Så kombinationen av kolumnerna grp och ö är en ö-identifierare, som du kan använda i ö-uppgifter som involverar gruppering efter ö när det är relevant.
Följande är resultatet av frågan som definierar C2:
grp ord val isstart island-------- ----- ---- -------- -------Grupp A 1002 Y 1 1Grupp A 1003 Y 0 1Grupp A 1005 Y 0 1Grupp A 1007 N 1 2Grupp A 1011 N 0 2Grupp A 1013 N 0 2Grupp A 1017 Y 1 3Grupp A 1019 Y 0 3Grupp A 104Group N 1023 N 104G 1 X 0 1023 N 1 X 0 1Grupp B 1003 Z 1 2Grupp B 1005 Z 0 2Grupp B 1008 Z 0 2Grupp B 1013 Z 0 2Grupp B 1021 Y 1 3Grupp B 1034 Y 0 3
Slutligen beräknar den yttre frågan den önskade resultatkolumnen seqno med en ROW_NUMBER-funktion, partitionerad av grp och island, och ordnad efter ord. Lägg märke till att denna kombination av partitionerings- och ordningselement skiljer sig från den som användes av de tidigare fönsterfunktionerna. Medan beräkningen av de två första fönsterfunktionerna potentiellt kan förlita sig på indexordning, kan den sista inte.
Figur 3 har standardplanen som jag fick för den här lösningen.
Figur 3:Parallellplan för känd lösning 2
Som du kan se i planen använder beräkningen av de två första fönsterfunktionerna strategin no sort + parallell radläge, och beräkningen av den sista använder strategin sort + parallell batchläge.
Körtiderna jag fick för den här lösningen varierade från 2,57 sekunder mot 1 miljoner rader till 46,2 sekunder mot 10 miljoner rader.
När jag tvingade fram seriell bearbetning fick jag planen som visas i figur 4.
Figur 4:Serieplan för känd lösning 2
Som väntat förlitar sig alla fönsterfunktionsberäkningar denna gång på batchlägesstrategin. De två första utan föregående sortering och de sista med. Både den parallella planen och den seriella involverade en explicit sorteringsoperator. Serieplanen fungerade bättre än parallellplanen på min maskin med ingångsstorlekarna jag testade. Körtiderna jag fick för den påtvingade serieplanen varierade från 1,75 sekunder mot 1 miljoner rader till 21,7 sekunder mot 10 miljoner rader.
Lösning baserad på ny teknik
När Erland introducerade denna utmaning i ett privat forum var folk skeptiska till möjligheten att lösa den med en fråga som hade optimerats med en plan utan explicit sortering. Det var allt jag behövde höra för att motivera mig. Så här är vad jag kom på:
MED C1 AS( SELECT *, ROW_NUMBER() ÖVER(PARTITION BY grp ORDER BY ord) AS rn, LAG(val) OVER(PARTITION BY GRP ORDER BY ord) AS prev FROM dbo.T1),C2 AS( SELECT *, MAX(CASE WHEN val =prev THEN NULL ELSE rn END) OVER(PARTITION BY grp ORDER BY ORD ROWS UNBOUNDED PRECEDING) AS firstrn FROM C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoFROM C2;Lösningen använder tre fönsterfunktioner:LAG, ROW_NUMBER och MAX. Det viktiga här är att alla tre är baserade på grp-partitionering och ord-ordning, vilket är anpassat till den stödjande indexnyckelordningen.
Frågan som definierar CTE C1 använder funktionen ROW_NUMBER för att beräkna radnummer (rn kolumn) och LAG-funktionen för att returnera föregående valvärde (prev kolumn).
Här är resultatet av frågan som definierar C1:
grp ord val rn prev-------- ----- ---- --- -----Grupp A 1002 Y 1 NULLGrupp A 1003 Y 2 YGrupp A 1005 Y 3 YGrupp A 1007 N 4 YGrupp A 1011 N 5 NGrupp A 1013 N 6 NGrupp A 1017 Y 7 NGrupp A 1019 Y 8 YGrupp A 1023 N 9 YGrupp A 1029 N 10 NGrupp B 1001 B 1001 B 1001 X00 X 3 1005 Z 4 ZGroup B 1008 Z 5 ZGroup B 1013 Z 6 ZGroup B 1021 Y 7 ZGroup B 1034 Y 8 YLägg märke till när val och prev är samma, det är inte den första raden på ön, annars är det det.
Baserat på denna logik använder frågan som definierar CTE C2 ett CASE-uttryck som returnerar rn när raden är den första på en ö och NULL annars. Koden tillämpar sedan MAX-fönsterfunktionen på resultatet av CASE-uttrycket och returnerar den första rn av ön (första kolumnen).
Här är utdata från frågan som definierar C2, inklusive utdata från CASE-uttrycket:
grp ord val rn prev CASE firstrn-------- ----- ---- --- ----- ----- --------Grupp A 1002 Y 1 NULL 1 1Grupp A 1003 Y 2 Y NULL 1Grupp A 1005 Y 3 Y NULL 1Grupp A 1007 N 4 Y 4 4Grupp A 1011 N 5 N NULL 4Grupp A 1013 N 1013 N 10 N 1 G 7 N 1 0 N 1 Y 8 Y NULL 7Grupp A 1023 N 9 Y 9 9Grupp A 1029 N 10 N NULL 9Grupp B 1001 X 1 NULL 1 1Grupp B 1002 X 2 X NULL 1Grupp B 1003 Z 0Group Z 0Group 1003 Z 0Gro 3 Z 04 5 Z NULL 3Grupp B 1013 Z 6 Z NULL 3Grupp B 1021 Y 7 Z 7 7Grupp B 1034 Y 8 Y NULL 7Det som återstår för den yttre frågan är att beräkna den önskade resultatkolumnen seqno som rn minus firstrn plus 1.
Figur 5 har den parallella standardplanen som jag fick för den här lösningen när jag testade den med en SELECT INTO-sats och skrev resultatet till en tillfällig tabell.
Figur 5:Parallell plan för en ny lösning
Det finns inga explicita sorteringsoperatörer i denna plan. Men alla tre fönsterfunktionerna beräknas med hjälp av strategin no sort + parallell radläge, så vi saknar fördelarna med batchbearbetning. Körtiderna jag fick för den här lösningen med parallellplanen varierade från 2,47 sekunder mot 1M rader och 41,4 mot 10M rader.
Här är det ganska stor sannolikhet för en seriell plan med batchbearbetning att göra betydligt bättre, speciellt när maskinen inte har många processorer. Minns att jag testar mina lösningar mot en maskin med 4 logiska processorer. Figur 6 har planen jag fick för den här lösningen när jag tvingade fram seriell bearbetning.
Figur 6:Serieplan för en ny lösning
Alla tre fönsterfunktionerna använder strategin no sort + seriell batch-läge. Och resultaten är ganska imponerande. Lösningens körtider varierade från 0,5 sekunder mot 1M rader och 5,49 sekunder mot 10M rader. Vad som också är nyfiket med den här lösningen är att när man testar den som en normal SELECT-sats (med resultat förkastade) i motsats till en SELECT INTO-sats, valde SQL Server serieplanen som standard. Med de andra två lösningarna fick jag en parallell plan som standard både med SELECT och med SELECT INTO.
Se nästa avsnitt för fullständiga resultat från prestandatest.
Prestandajämförelse
Här är koden som jag använde för att testa de tre lösningarna, givetvis utan att kommentera MAXDOP 1-tipset för att testa serieplanerna:
-- Testa känd lösning 1 SLIPP TABELL OM FINNS #Resultat; MED C AS( SELECT *, ROW_NUMBER() ÖVER(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) SOM sekvensINTO #ResultFROM C/* OPTION(MAXDOP 1) */; -- avkommentar för seriell testGO -- Testa känd lösning 2 SLIPP TABELL OM FINNS #Resultat; MED C1 AS( SELECT *, CASE NÄR val =LAG(val) OVER(PARTITION BY GRP ORDER BY ord) DÅ 0 ANDERS 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(ärstart) OVER(PARTITION BY GRP ORDER BY ORD ROWS UNBOUNDED PRECEDING) AS island FROM C1)SELECT grp, ord, val, ROW_NUMBER() OVER(PARTITION BY GRP, island ORDER BY Ord) AS seqnoINTO #ResultFROM C2/* OPTION(MAXDOP 1) */; GÅ -- Testa ny lösning SLIPP TABELL OM FINNS #Resultat; MED C1 AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY GRP ORDER BY ord) AS rn, LAG(val) OVER(PARTITION BY GRP ORDER BY ord) AS prev FROM dbo.T1),C2 AS( SELECT *, MAX (CASE WHEN val =prev THEN NULL ELSE rn END) OVER(PARTITION BY GRP ORDER BY Ord ROWS UNBOUNDED PRECEDING) AS firstrn FROM C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoINTO #ResultFROM C2/* OPTION( MAXDOP 1) */;Figur 7 visar körtiderna för både de parallella och seriella planerna för alla lösningar mot olika ingångsstorlekar.
Figur 7:Prestandajämförelse
Den nya lösningen, med seriellt läge, är den klara vinnaren. Den har bra prestanda, linjär skalning och omedelbar responstid.
Slutsats
Öar uppgifter är ganska vanliga i verkliga livet. Många av dem handlar både om att identifiera öar och att gruppera data efter ön. Erlands ö-utmaning, som var fokus i den här artikeln, är lite mer unik eftersom den inte involverar gruppering utan istället sekvenserar varje ös rader med radnummer.
Jag presenterade två lösningar baserade på kända tekniker för att identifiera öar. Problemet med båda är att de resulterar i planer som involverar explicit sortering, vilket negativt påverkar prestanda, skalbarhet och svarstid för lösningarna. Jag presenterade också en ny teknik som resulterade i en plan utan sortering alls. Serieplanen för denna lösning, som använder en no sort + seriell batch-lägesstrategi, har utmärkt prestanda, linjär skalning och omedelbar svarstid. Det är olyckligt, åtminstone för nu, att vi inte kan ha en no sort + parallell batch-lägesstrategi för att hantera fönsterfunktioner.