Rijnummers met niet-deterministische volgorde

De ROW_NUMBER-vensterfunctie heeft talloze praktische toepassingen, veel verder dan alleen de voor de hand liggende rangschikkingsbehoeften. Meestal, wanneer u rijnummers berekent, moet u deze berekenen op basis van een bepaalde volgorde, en u geeft de gewenste bestelspecificatie op in de venstervolgordeclausule van de functie. Er zijn echter gevallen waarin u rijnummers in willekeurige volgorde moet berekenen; met andere woorden, gebaseerd op niet-deterministische orde. Dit kan over het hele zoekresultaat zijn, of binnen partities. Voorbeelden zijn onder meer het toewijzen van unieke waarden aan resultaatrijen, het ontdubbelen van gegevens en het retourneren van elke rij per groep.

Merk op dat het toewijzen van rijnummers op basis van niet-deterministische volgorde anders is dan het toewijzen van rijnummers op basis van willekeurige volgorde. Bij het eerste maakt het u niet uit in welke volgorde ze zijn toegewezen en of bij herhaalde uitvoeringen van de query steeds dezelfde rijnummers aan dezelfde rijen worden toegewezen of niet. Bij het laatste verwacht je dat herhaalde uitvoeringen steeds veranderen welke rijen worden toegewezen met welke rijnummers. Dit artikel onderzoekt verschillende technieken voor het berekenen van rijnummers met een niet-deterministische volgorde. De hoop is om een techniek te vinden die zowel betrouwbaar als optimaal is.

Speciale dank aan Paul White voor de tip betreffende constant folden, voor de runtime constant-techniek, en omdat je altijd een geweldige bron van informatie bent!

Wanneer orde belangrijk is

Ik begin met gevallen waarin de volgorde van rijnummers er toe doet.

Ik gebruik een tabel met de naam T1 in mijn voorbeelden. Gebruik de volgende code om deze tabel te maken en vul deze met voorbeeldgegevens:

Beschouw de volgende query (we noemen het Query 1):

SELECT id, grp, datacol, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n FROM dbo.T1;

Hier wilt u dat rijnummers worden toegewezen binnen elke groep geïdentificeerd door de kolom grp, gesorteerd op de kolom datacol. Toen ik deze vraag op mijn systeem uitvoerde, kreeg ik de volgende output:

id grp datacol n--- ---- -------- ---5 A 40 12 A 50 211 A 50 37 B 10 13 B 20 2

Rijnummers worden hier in een gedeeltelijk deterministische en gedeeltelijk niet-deterministische volgorde toegewezen. Wat ik hiermee bedoel is dat je de zekerheid hebt dat binnen dezelfde partitie een rij met een hogere datacolwaarde een grotere rijnummerwaarde krijgt. Aangezien datacol echter niet uniek is binnen de grp-partitie, is de volgorde van toewijzing van rijnummers aan rijen met dezelfde grp- en datacol-waarden niet-deterministisch. Dat is het geval met de rijen met id-waarden 2 en 11. Beide hebben de grp-waarde A en de datacol-waarde 50. Toen ik deze query voor het eerst op mijn systeem uitvoerde, kreeg de rij met id 2 rijnummer 2 en de rij met id 11 heeft rij nummer 3. Let niet op de waarschijnlijkheid dat dit in de praktijk in SQL Server gebeurt; als ik de query opnieuw uitvoer, zou theoretisch de rij met id 2 kunnen worden toegewezen aan rijnummer 3 en de rij met id 11 zou kunnen worden toegewezen aan rijnummer 2.

Als u rijnummers moet toewijzen op basis van op een volledig deterministische volgorde, die herhaalbare resultaten garandeert bij alle uitvoeringen van de query, zolang de onderliggende gegevens niet veranderen, moet de combinatie van elementen in de vensterpartitionering en ordeningsclausules uniek zijn. Dit zou in ons geval kunnen worden bereikt door de kolom-id toe te voegen aan de raamvolgordeclausule als een tiebreaker. De OVER-clausule zou dan zijn:

OVER (PARTITION BY grp ORDER BY datacol, id)

Bij het berekenen van rijnummers op basis van een zinvolle bestelspecificatie zoals in Query 1, moet SQL Server in ieder geval de rijen geordend door de combinatie van raamverdeling en ordeningselementen. Dit kan worden bereikt door de voorbestelde gegevens uit een index te halen of door de gegevens te sorteren. Momenteel is er geen index op T1 om de ROW_NUMBER-berekening in Query 1 te ondersteunen, dus SQL Server moet ervoor kiezen om de gegevens te sorteren. Dit is te zien in het plan voor Query 1 dat wordt weergegeven in Figuur 1.

Figuur 1: Plan voor Query 1 zonder een ondersteunende index

Merk op dat het plan de gegevens van de geclusterde index scant met de eigenschap Ordered: False. Dit betekent dat de scan niet de rijen hoeft te retourneren die zijn gerangschikt op de indexsleutel. Dat is het geval omdat de geclusterde index hier wordt gebruikt alleen omdat deze de zoekopdracht dekt en niet vanwege de sleutelvolgorde. Het plan past dan een sortering toe, wat resulteert in extra kosten, N Log N-schaling en vertraagde responstijd. De segmentoperator produceert een vlag die aangeeft of de rij de eerste in de partitie is of niet. Ten slotte wijst de Sequence Project-operator rijnummers toe die beginnen met 1 in elke partitie.

Als u niet wilt sorteren, kunt u een dekkingsindex voorbereiden met een sleutellijst die is gebaseerd op de partitionerings- en ordeningselementen, en een include-lijst die is gebaseerd op de dekkingselementen.Ik beschouw deze index graag als een POC-index (voor partitionering, ordening en dekking). Hier is de definitie van de POC die onze zoekopdracht ondersteunt:

CREATE INDEX idx_grp_data_i_id ON dbo.T1(grp, datacol) INCLUDE(id);

Voer query 1 opnieuw uit:

SELECT id, grp, datacol, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n FROM dbo.T1;

Het plan voor deze uitvoering wordt getoond in Figuur 2.

Figuur 2: Plan voor Query 1 met een POC-index

Merk op dat het plan deze keer de POC-index scant met de eigenschap Ordered: True. Dit betekent dat de scan garandeert dat de rijen in de volgorde van de indexsleutel worden geretourneerd. Omdat de gegevens vooraf uit de index worden gehaald, zoals de vensterfunctie nodig heeft, is expliciet sorteren niet nodig. De schaal van dit plan is lineair en de responstijd is goed.

Wanneer de volgorde er niet toe doet

Het wordt een beetje lastig als je rijnummers moet toewijzen met een volledig niet-deterministische volgorde. Het natuurlijke dat u in een dergelijk geval wilt doen, is de functie ROW_NUMBER gebruiken zonder een venstervolgordeclausule op te geven. Laten we eerst kijken of de SQL-standaard dit toestaat. Hier is het relevante deel van de standaard waarin de syntaxisregels voor window functies:

Merk op dat item 6 de functies < ntile functie >, lead- of lag-functie >, < rangschikking functietype > of ROW_NUMBER, en dan zegt item 6a dat voor de functies < de ntile functie >, < lead- of lag-functie >, RANK of DENSE_RANK de window order clausule zal b e aanwezig. Er is geen expliciete taal waarin staat of ROW_NUMBER een raamvolgorde-clausule vereist of niet, maar de vermelding van de functie in item 6 en het weglaten ervan in 6a zou kunnen impliceren dat de clausule optioneel is voor deze functie. Het is vrij duidelijk waarom functies zoals RANK en DENSE_RANK een raamvolgordeclausule zouden vereisen, aangezien deze functies gespecialiseerd zijn in het afhandelen van stropdassen en stropdassen alleen bestaan als er bestelspecificatie is. U zou echter zeker kunnen zien hoe de ROW_NUMBER-functie zou kunnen profiteren van een optionele venstervolgorde-clausule.

Laten we het dus proberen en proberen rijnummers te berekenen zonder venstervolgorde in SQL Server:

SELECT id, grp, datacol, ROW_NUMBER() OVER() AS n FROM dbo.T1;

Deze poging resulteert in de volgende fout:

Msg 4112, Level 15, State 1, Line 53
De functie “ROW_NUMBER” moet een OVER-clausule hebben met ORDER BY.

Inderdaad, als u de SQL Server-documentatie van de ROW_NUMBER-functie controleert, vindt u de volgende tekst:

“order_by_clause

De ORDER De BY-clausule bepaalt de volgorde waarin de rijen hun unieke ROW_NUMBER binnen een gespecificeerde partitie krijgen toegewezen. Het is vereist. ”

Dus blijkbaar is de window order-clausule verplicht voor de ROW_NUMBER-functie in SQL Server . Dat is trouwens ook het geval in Oracle.

Ik moet zeggen dat ik niet zeker weet of ik de reden begrijp ng achter deze vereiste. Onthoud dat je rijnummers kunt definiëren op basis van een gedeeltelijk niet-deterministische volgorde, zoals in vraag 1. Dus waarom zou je non-determinisme niet helemaal toestaan? Misschien is er een reden waar ik niet aan denk. Als u een dergelijke reden kunt bedenken, deel deze dan alstublieft.

In ieder geval zou u kunnen stellen dat als u niet geeft om de bestelling, aangezien de raambestelingsclausule verplicht is, u elke bestellen. Het probleem met deze aanpak is dat als u een kolom uit de opgevraagde tabel (len) bestelt, dit een onnodige prestatieverbinding met zich meebrengt. Als er geen ondersteunende index is, betaalt u voor expliciete sortering. Als er een ondersteunende index is, beperkt u de opslagengine tot een scanstrategie voor de indexvolgorde (volgens de aan de index gekoppelde lijst). U staat het niet meer flexibiliteit toe zoals gewoonlijk wanneer de volgorde er niet toe doet bij het kiezen tussen een indexbestelscan en een toewijzingsopdrachtscan (op basis van IAM-pagina’s).

Een idee dat het proberen waard is is het specificeren van een constante, zoals 1, in de window order clausule. Indien ondersteund, hoop je dat de optimizer slim genoeg is om te beseffen dat alle rijen dezelfde waarde hebben, zodat er geen echte ordeningsrelevantie is en daarom geen noodzaak om een sorteer- of indexvolgorde-scan te forceren. Hier is een vraag die deze benadering probeert:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 1) AS n FROM dbo.T1;

Helaas ondersteunt SQL Server deze oplossing niet. Het genereert de volgende fout:

Msg 5308, Level 16, State 1, Line 56
Windowed-functies, aggregaten en NEXT VALUE FOR-functies ondersteunen geen indices van gehele getallen als ORDER BY-clausule-expressies.

Blijkbaar gaat SQL Server ervan uit dat als u een integerconstante gebruikt in de window order-clausule, deze een rangschikking van een element in de SELECT-lijst vertegenwoordigt, zoals wanneer u een geheel getal specificeert in de presentatie ORDER BY-clausule. Als dat het geval is, is een andere optie die de moeite waard is om te proberen, het specificeren van een niet-integer constante, zoals:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY "No Order") AS n FROM dbo.T1;

Blijkt dat deze oplossing ook niet wordt ondersteund. SQL Server genereert de volgende fout:

Msg 5309, Level 16, State 1, Line 65
Windowed-functies, aggregaten en NEXT VALUE FOR-functies ondersteunen geen constanten als ORDER BY-clausule-expressies.

Blijkbaar ondersteunt de window order clausule geen enkele constante.

Tot dusver hebben we het volgende geleerd over de ROW_NUMBER-functie van de venstervolgorde van relevantie in SQL Server:

  1. ORDER BY is vereist.
  2. Kan niet ordenen op een integerconstante, aangezien SQL Server denkt dat je probeert een rangschikkingspositie op te geven in de SELECT.
  3. Kan niet ordenen op elke soort constante.

De conclusie is dat je moet ordenen op uitdrukkingen die geen constanten zijn. Het is duidelijk dat je kunt sorteren op een kolomlijst uit de opgevraagde tabel (len). Maar we zijn op zoek naar een efficiënte oplossing waarbij de optimizer kan beseffen dat er geen ordeningsrelevantie is.

Constant vouwen

De conclusie tot nu toe is dat je geen constanten kunt gebruiken in de ROW_NUMBER’s venstervolgorde-clausule, maar hoe zit het met expressies op basis van constanten, zoals in de volgende query:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 1+0) AS n FROM dbo.T1;

Deze poging wordt echter het slachtoffer van een proces dat bekend staat als constant vouwen, wat normaal gesproken een positieve invloed heeft op de prestaties van zoekopdrachten. Het idee achter deze techniek is om de prestaties van query’s te verbeteren door een uitdrukking op basis van constanten in een vroeg stadium van de queryverwerking in hun resultaatconstanten te vouwen. U kunt hier details vinden over welke soorten uitdrukkingen constant kunnen worden gevouwen. Onze uitdrukking 1 + 0 wordt omgevouwen tot 1, wat resulteert in dezelfde fout die u kreeg bij het rechtstreeks specificeren van de constante 1:

Msg 5308, niveau 16, staat 1, regel 79
Vensterfuncties, aggregaten en NEXT VALUE FOR-functies ondersteunen geen integer-indices als ORDER BY-clausule-expressies.

U zou met een vergelijkbare situatie te maken krijgen wanneer u probeert twee letterlijke tekenreeksen samen te voegen, zoals:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY "No" + " Order") AS n FROM dbo.T1;

Je krijgt dezelfde fout die je kreeg als je de letterlijke “No Order” rechtstreeks specificeerde:

Msg 5309, Level 16, State 1, Line 55
Windowed functies, aggregaten en NEXT VALUE FOR-functies ondersteunen geen constanten als ORDER BY-clausule-expressies.

Bizarro-wereld – fouten die fouten voorkomen

Het leven zit vol verrassingen …

Een ding dat constant vouwen voorkomt, is wanneer de uitdrukking normaal gesproken resulteert in een fout. De uitdrukking 2147483646 + 1 kan bijvoorbeeld constant worden gevouwen, omdat dit resulteert in een geldige INT-getypte waarde. Bijgevolg mislukt een poging om de volgende zoekopdracht uit te voeren:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 2147483646+1) AS n FROM dbo.T1;

Msg 5308, niveau 16, staat 1, regel 109
Functies in vensters, aggregaten en VOLGENDE WAARDE FOR-functies ondersteunen geen integer-indices als ORDER BY-clausule-expressies.

De expressie 2147483647 + 1 kan echter niet constant worden gevouwen omdat een dergelijke poging zou hebben geresulteerd in een INT-overflowfout. De implicatie voor bestellen is best interessant. Probeer de volgende zoekopdracht (we noemen deze Query 2):

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 2147483647+1) AS n FROM dbo.T1;

Vreemd genoeg wordt deze zoekopdracht succesvol uitgevoerd! Wat er gebeurt, is dat SQL Server enerzijds geen constant vouwen toepast, en daarom is de volgorde gebaseerd op een uitdrukking die geen enkele constante is. Aan de andere kant geeft de optimizer aan dat de ordeningswaarde hetzelfde is voor alle rijen, dus het negeert de ordeningsuitdrukking helemaal. Dit wordt bevestigd bij het onderzoeken van het plan voor deze query, zoals weergegeven in figuur 3.

Figuur 3: plan voor query 2

Observeer dat het plan een dekkingsindex scant met de eigenschap Ordered: False. Dit was precies ons prestatiedoel.

Op een vergelijkbare manier omvat de volgende zoekopdracht een geslaagde constante vouwpoging en mislukt daarom:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 1/1) AS n FROM dbo.T1;

Msg 5308, niveau 16, staat 1, regel 123
Functies in vensters, aggregaten en NEXT VALUE FOR-functies ondersteunen geen indices van gehele getallen als ORDER BY-clausule-expressies.

De volgende vraag heeft betrekking op een mislukte constante vouwpoging, en slaagt daarom, door het plan te genereren dat eerder in figuur 3 is weergegeven:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 1/0) AS n FROM dbo.T1;

De volgende query omvat een succesvolle constante vouwpoging (VARCHAR letterlijke “1” wordt impliciet geconverteerd naar de INT 1, en dan wordt 1 + 1 gefold naar 2), en mislukt daarom:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 1+"1") AS n FROM dbo.T1;

Msg 5308, Level 16, State 1, Line 134
Windowed-functies, aggregaten en NEXT VALUE FOR-functies ondersteunen geen integer-indices als ORDER BY-clausule-expressies.

De volgende query heeft betrekking op een mislukte constante vouwpoging (kan “A” niet converteren naar INT), en slaagt daarom, het genereren van het plan dat eerder in figuur 3 is weergegeven:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY 1+"A") AS n FROM dbo.T1;

Om eerlijk te zijn, hoewel deze bizarre techniek ons oorspronkelijke prestatiedoel bereikt, kan ik niet zeggen dat ik het als veilig beschouw en daarom vertrouw ik er niet zo gemakkelijk op.

Runtimeconstanten gebaseerd op functies

Voortgaand op het zoeken naar een goede oplossing voor het berekenen van rijnummers met niet-deterministische volgorde, zijn er een paar technieken die veiliger lijken dan de laatste eigenzinnige oplossing: gebruik van runtime-constanten op basis van functies, gebruik van een subquery op basis van een constante, gebruik een gealiaste kolom gebaseerd op een constante en met behulp van een variabele.

Zoals ik in T-SQL uitleg, bugs, valkuilen en best practices – determinisme, worden de meeste functies in T-SQL slechts één keer per referentie in de query geëvalueerd, niet één keer per rij. Dit is zelfs het geval met de meeste niet-deterministische functies zoals GETDATE en RAND. Er zijn zeer weinig uitzonderingen op deze regel, zoals de functies NEWID en CRYPT_GEN_RANDOM, die wel eens per rij worden geëvalueerd. De meeste functies, zoals GETDATE, @@ SPID en vele andere, worden aan het begin van de query één keer geëvalueerd en hun waarden worden vervolgens beschouwd als runtime-constanten. Een verwijzing naar dergelijke functies wordt niet constant gevouwen. Deze kenmerken maken een runtime-constante die is gebaseerd op een functie een goede keuze als het element voor het ordenen van vensters, en het lijkt erop dat T-SQL dit inderdaad ondersteunt. Tegelijkertijd realiseert de optimizer zich dat er in de praktijk geen bestelrelevantie is, waardoor onnodige prestatieboetes worden vermeden.

Hier is een voorbeeld met de GETDATE-functie:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY GETDATE()) AS n FROM dbo.T1;

Deze query krijgt hetzelfde plan als eerder in figuur 3.

Hier is nog een voorbeeld met de @@ SPID-functie (retourneert de huidige sessie-ID):

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY @@SPID) AS n FROM dbo.T1;

Hoe zit het met de functie PI? Probeer de volgende vraag:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY PI()) AS n FROM dbo.T1;

Deze mislukt met de volgende fout:

Msg 5309, Level 16, State 1, Line 153
Windowed-functies, aggregaten en NEXT VALUE FOR-functies ondersteunen geen constanten als ORDER BY-clausule-expressies.

Functies zoals GETDATE en @@ SPID worden eenmaal per uitvoering van het plan opnieuw geëvalueerd, zodat ze niet constant gevouwen. PI vertegenwoordigt altijd dezelfde constante en wordt daarom constant gevouwen.

Zoals eerder vermeld, zijn er maar heel weinig functies die eenmaal per rij worden geëvalueerd, zoals NEWID en CRYPT_GEN_RANDOM. Dit maakt ze een slechte keuze als het element voor het ordenen van vensters als je een niet-deterministische volgorde nodig hebt – niet te verwarren met een willekeurige volgorde. Waarom een onnodige sorteerboete betalen?

Hier is een voorbeeld waarbij de functie NEWID wordt gebruikt:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY NEWID()) AS n FROM dbo.T1;

Het plan voor deze query wordt weergegeven in Figuur 4, waarmee wordt bevestigd dat SQL Server expliciete sorteren op basis van het resultaat van de functie.

Figuur 4: Plan voor Query 3

Als u de rijnummers wilt toewijzen in willekeurige volgorde, dat is in elk geval de techniek die u wilt gebruiken. U moet zich er alleen van bewust zijn dat dit de sorteerkosten met zich meebrengt.

Een subquery gebruiken

U kunt ook een subquery gebruiken die is gebaseerd op een constante als expressie voor de volgorde van het venster (bijv. ORDER BY (KIES “Geen bestelling”)). Ook bij deze oplossing erkent de optimalisatie van SQL Server dat er geen ordeningsrelevantie is, en legt daarom geen onnodige sortering op en beperkt de keuzes van de opslagengine niet tot de keuzes die de bestelling moeten garanderen. Probeer de volgende zoekopdracht als voorbeeld uit te voeren:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY (SELECT "No Order")) AS n FROM dbo.T1;

Je krijgt hetzelfde plan als eerder in figuur 3.

Een van de grote voordelen van deze techniek is dat u uw eigen persoonlijke touch kunt toevoegen.Misschien vind je NULLs echt leuk:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM dbo.T1;

Misschien vind je een bepaald nummer echt leuk:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY (SELECT 42)) AS n FROM dbo.T1;

Misschien wil je iemand een bericht sturen:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY (SELECT "Lilach, will you marry me?")) AS n FROM dbo.T1;

Je begrijpt het wel.

Goed te doen, maar lastig

Er zijn een aantal technieken die werken, maar een beetje onhandig zijn. Een daarvan is om een kolomalias te definiëren voor een uitdrukking op basis van een constante, en vervolgens die kolomalias te gebruiken als het element voor het ordenen van vensters. U kunt dit doen met een tabelexpressie of met de CROSS APPLY-operator en een tabelwaardeconstructor. Hier is een voorbeeld voor het laatste:

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY ) AS n FROM dbo.T1 CROSS APPLY ( VALUES("No Order") ) AS A();

Je krijgt hetzelfde plan als eerder in figuur 3.

Een andere optie is om een variabele te gebruiken als het element voor het ordenen van vensters:

DECLARE @ImABitUglyToo AS INT = NULL; SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY @ImABitUglyToo) AS n FROM dbo.T1;

Deze query krijgt ook het plan dat eerder in figuur 3 is weergegeven.

Wat als ik mijn eigen UDF gebruik? ?

Je zou kunnen denken dat het gebruik van je eigen UDF die een constante retourneert een goede keuze zou kunnen zijn als het venster-ordeningselement als je een niet-deterministische volgorde wilt, maar dat is het niet. Beschouw de volgende UDF-definitie als voorbeeld:

DROP FUNCTION IF EXISTS dbo.YouWillRegretThis;GO CREATE FUNCTION dbo.YouWillRegretThis() RETURNS INTASBEGIN RETURN NULLEND;GO

Probeer de UDF als de raamvolgordeclausule te gebruiken, zoals zo (we noemen deze Query 4):

SELECT id, grp, datacol, ROW_NUMBER() OVER(ORDER BY dbo.YouWillRegretThis()) AS n FROM dbo.T1;

Voorafgaand aan SQL Server 2019 (of parallel compatibiliteitsniveau < 150), worden door de gebruiker gedefinieerde functies geëvalueerd per rij . Zelfs als ze een constante retourneren, raken ze niet in lijn. U kunt dus enerzijds een dergelijke UDF gebruiken als het element voor het ordenen van vensters, maar aan de andere kant resulteert dit in een soort penalty. Dit wordt bevestigd door het plan voor deze query te onderzoeken, zoals weergegeven in figuur 5.

Figuur 5: plan voor query 4

Beginnend met SQL Server 2019, onder compatibiliteitsniveau > = 150, worden dergelijke door de gebruiker gedefinieerde functies inline gezet, wat meestal een goede zaak is, maar in ons geval resulteert in een fout:

Msg 5309, Level 16, State 1, Line 217
Windowed-functies, aggregaten en NEXT VALUE FOR-functies ondersteunen geen constanten als ORDER BY-clausule-expressies.

Dus het gebruik van een UDF gebaseerd op een constante, aangezien het element voor het ordenen van vensters een sortering of een fout afdwingt, afhankelijk van de versie van SQL Server die u gebruikt en uw databasecompatibiliteitsniveau. Kort gezegd, doe dit niet.

Gepartitioneerde rijnummers met niet-deterministische volgorde

Een veelvoorkomend geval voor gepartitioneerde rijnummers op basis van niet-deterministische volgorde is het retourneren van elke rij per groep. Gegeven dat in dit scenario per definitie een partitioneringselement bestaat, zou je denken dat het in zo’n geval een veilige techniek zou zijn om het vensterpartitioneringselement ook als het venstervolgorde-element te gebruiken. Als eerste stap berekent u rijnummers als volgt:

SELECT id, grp, datacol, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n FROM dbo.T1;

Het plan voor deze query wordt getoond in figuur 6.

Figuur 6: Plan voor Query 5

De reden dat onze ondersteunende index wordt gescand met een Ordered: True-eigenschap is omdat SQL Server de rijen van elke partitie moet verwerken als een eenheid. Dat is het geval voorafgaand aan het filteren. Als u slechts één rij per partitie filtert, heeft u zowel op volgorde gebaseerde als op hash gebaseerde algoritmen als opties.

De tweede stap is om de query met de rijnummerberekening in een tabelexpressie te plaatsen, en in de buitenste query filtert de rij met rijnummer 1 in elke partitie, als volgt:

Theoretisch zou deze techniek veilig moeten zijn, maar Paul White vond een bug die laat zien dat je met deze methode attributen kunt krijgen van verschillende bronrijen in de geretourneerde resultaatrij per partitie. Het gebruik van een runtime-constante gebaseerd op een functie of een subquery gebaseerd op een constante, aangezien het ordeningselement veilig lijkt te zijn, zelfs in dit scenario, dus zorg ervoor dat je in plaats daarvan een oplossing zoals de volgende gebruikt:

Niemand zal deze kant op gaan zonder mijn toestemming

Het is een algemene behoefte om rijnummers te berekenen op basis van een niet-deterministische volgorde. Het zou leuk geweest zijn als T-SQL de venstervolgorde-clausule eenvoudigweg optioneel had gemaakt voor de functie ROW_NUMBER, maar dat is niet zo. Als dat niet het geval was, zou het leuk zijn geweest als het in ieder geval was toegestaan om een constante als ordeningselement te gebruiken, maar dat wordt ook niet ondersteund.Maar als je het netjes vraagt, in de vorm van een subquery op basis van een constante of een runtime-constante op basis van een functie, staat SQL Server het toe. Dit zijn de twee opties waar ik me het meest comfortabel bij voel. Ik voel me niet echt op mijn gemak bij de eigenzinnige foutieve uitdrukkingen die lijken te werken, dus ik kan deze optie niet aanbevelen.

Leave a Reply

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *