Numeri di riga con ordine non deterministico

La funzione finestra ROW_NUMBER ha numerose applicazioni pratiche, ben oltre le ovvie esigenze di classificazione. La maggior parte delle volte, quando si calcolano i numeri di riga, è necessario calcolarli in base a un certo ordine e fornire la specifica di ordinamento desiderata nella clausola di ordine della finestra della funzione. Tuttavia, ci sono casi in cui è necessario calcolare i numeri di riga senza un ordine particolare; in altre parole, basato su un ordine non deterministico. Questo potrebbe essere sull’intero risultato della query o all’interno di partizioni. Gli esempi includono l’assegnazione di valori univoci alle righe dei risultati, la deduplicazione dei dati e la restituzione di qualsiasi riga per gruppo.

Nota che la necessità di assegnare numeri di riga in base a un ordine non deterministico è diversa dalla necessità di assegnarli in base a un ordine casuale. Con il primo, semplicemente non ti interessa in quale ordine vengono assegnati e se le esecuzioni ripetute della query continuano ad assegnare gli stessi numeri di riga alle stesse righe o meno. Con quest’ultimo, ti aspetti che le esecuzioni ripetute continuino a cambiare quali righe vengono assegnate con quali numeri di riga. Questo articolo esplora diverse tecniche per calcolare i numeri di riga con un ordine non deterministico. La speranza è trovare una tecnica che sia affidabile e ottimale.

Un ringraziamento speciale a Paul White per il suggerimento riguardante il piegamento costante, per la tecnica della costante di runtime e per essere sempre un’ottima fonte di informazioni!

Quando l’ordine è importante

Inizierò con i casi in cui l’ordinamento del numero di riga è importante.

Nei miei esempi userò una tabella chiamata T1. Utilizza il codice seguente per creare questa tabella e popolarla con dati di esempio:

Considera la seguente query (la chiameremo Query 1):

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

Qui si desidera assegnare i numeri di riga all’interno di ogni gruppo identificato dalla colonna grp, ordinati in base alla colonna datacol. Quando ho eseguito questa query sul mio sistema, ho ottenuto il seguente output:

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

I numeri di riga sono assegnati qui in un ordine parzialmente deterministico e parzialmente non deterministico. Ciò che intendo con questo è che hai la certezza che all’interno della stessa partizione, una riga con un valore datacol maggiore otterrà un valore numerico di riga maggiore. Tuttavia, poiché datacol non è univoco all’interno della partizione grp, l’ordine di assegnazione dei numeri di riga tra le righe con gli stessi valori grp e datacol non è deterministico. Questo è il caso delle righe con valori id 2 e 11. Entrambe hanno il valore grp A e il valore datacol 50. Quando ho eseguito questa query sul mio sistema per la prima volta, la riga con id 2 ha ottenuto la riga numero 2 e il la riga con ID 11 ha ottenuto la riga numero 3. Non importa la probabilità che ciò avvenga nella pratica in SQL Server; se eseguo di nuovo la query, in teoria, la riga con id 2 potrebbe essere assegnata con la riga numero 3 e la riga con id 11 potrebbe essere assegnata con la riga numero 2.

Se è necessario assegnare i numeri di riga in base su un ordine completamente deterministico, garantendo risultati ripetibili tra le esecuzioni della query fintanto che i dati sottostanti non cambiano, è necessario che la combinazione di elementi nelle clausole di partizionamento e ordinamento delle finestre sia univoca. Ciò potrebbe essere ottenuto nel nostro caso aggiungendo l’id della colonna alla clausola dell’ordine della finestra come tiebreaker. La clausola OVER sarebbe quindi:

OVER (PARTITION BY grp ORDER BY datacol, id)

In ogni caso, quando si calcolano i numeri di riga sulla base di alcune specifiche di ordinamento significative come nella Query 1, SQL Server deve elaborare il righe ordinate dalla combinazione di elementi di partizionamento delle finestre e di ordinamento. Ciò può essere ottenuto estraendo i dati preordinati da un indice o ordinando i dati. Al momento non esiste un indice su T1 per supportare il calcolo ROW_NUMBER nella query 1, quindi SQL Server deve optare per l’ordinamento dei dati. Questo può essere visto nel piano per Query 1 mostrato nella Figura 1.

Figura 1: Piano per Query 1 senza un indice di supporto

Notare che il piano analizza i dati dall’indice cluster con una proprietà Ordered: False. Ciò significa che la scansione non ha bisogno di restituire le righe ordinate dalla chiave di indice. Questo è il caso poiché l’indice cluster viene utilizzato qui solo perché copre la query e non a causa del suo ordine di chiavi. Il piano quindi applica un ordinamento, con conseguente costo aggiuntivo, ridimensionamento N Log N e tempi di risposta ritardati. L’operatore Segmento produce un flag che indica se la riga è la prima nella partizione o meno. Infine, l’operatore Sequence Project assegna i numeri di riga che iniziano con 1 in ogni partizione.

Se si desidera evitare la necessità di ordinare, è possibile preparare un indice di copertura con un elenco di chiavi basato sugli elementi di partizionamento e di ordinamento e un elenco di inclusione basato sugli elementi di copertura.Mi piace pensare a questo indice come a un indice POC (per il partizionamento, l’ordinamento e la copertura). Ecco la definizione del POC che supporta la nostra query:

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

Esegui di nuovo la query 1:

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

Il piano per questa esecuzione è mostrato nella Figura 2.

Figura 2: Piano per Query 1 con un indice POC

Si noti che questa volta il piano esegue la scansione dell’indice POC con una proprietà Ordered: True. Ciò significa che la scansione garantisce che le righe verranno restituite nell’ordine della chiave dell’indice. Poiché i dati vengono estratti preordinati dall’indice come richiede la funzione finestra, non è necessario un ordinamento esplicito. Il ridimensionamento di questo piano è lineare e il tempo di risposta è buono.

Quando l’ordine non ha importanza

Le cose si fanno un po ‘complicate quando è necessario assegnare numeri di riga con un carattere completamente non deterministico order. La cosa naturale da fare in questo caso è usare la funzione ROW_NUMBER senza specificare una clausola di window order. Innanzitutto, controlliamo se lo standard SQL lo consente. Ecco la parte rilevante dello standard che definisce le regole di sintassi per window funzioni:

Notare che l’elemento 6 elenca le funzioni < ntile function >, < funzione di anticipo o ritardo >, < tipo di funzione di classificazione > o ROW_NUMBER, quindi l’elemento 6a dice che per le funzioni < ntile function >, < funzione lead o lag >, RANK o DENSE_RANK la clausola sull’ordine della finestra b e presente. Non esiste un linguaggio esplicito che indichi se ROW_NUMBER richiede o meno una clausola di ordine della finestra, ma la menzione della funzione nell’elemento 6 e la sua omissione in 6a potrebbe implicare che la clausola è facoltativa per questa funzione. È abbastanza ovvio il motivo per cui funzioni come RANK e DENSE_RANK richiederebbero una clausola di ordine della finestra, poiché queste funzioni sono specializzate nella gestione dei legami e i legami esistono solo quando esiste una specifica di ordinamento. Tuttavia, potresti sicuramente vedere come la funzione ROW_NUMBER potrebbe trarre vantaggio da una clausola facoltativa per l’ordine delle finestre.

Quindi, proviamoci e proviamo a calcolare i numeri di riga senza l’ordinamento delle finestre in SQL Server:

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

Questo tentativo genera il seguente errore:

Msg 4112, livello 15, stato 1, riga 53
La funzione “ROW_NUMBER” deve avere una clausola OVER con ORDER BY.

Infatti, se controlli la documentazione di SQL Server della funzione ROW_NUMBER, troverai il seguente testo:

“order_by_clause

L’ORDINE La clausola BY determina la sequenza in cui alle righe viene assegnato il loro ROW_NUMBER univoco all’interno di una partizione specificata. È obbligatorio. “

Quindi apparentemente la clausola dell’ordine delle finestre è obbligatoria per la funzione ROW_NUMBER in SQL Server . Questo è anche il caso di Oracle, comunque.

Devo dire che non sono sicuro di aver capito le ragioni ng dietro questo requisito. Ricorda che stai consentendo di definire i numeri di riga in base a un ordine parzialmente non deterministico, come nella Query 1. Allora perché non consentire completamente il non determinismo? Forse c’è qualche motivo a cui non sto pensando. Se riesci a pensare a un motivo del genere, per favore condividi.

In ogni caso, potresti sostenere che se non ti interessa l’ordine, dato che la clausola di window order è obbligatoria, puoi specificare qualsiasi ordine. Il problema con questo approccio è che se si ordina in base a qualche colonna dalla tabella o dalle tabelle interrogate, ciò potrebbe comportare una riduzione delle prestazioni non necessaria. Quando non è presente un indice di supporto, pagherai per l’ordinamento esplicito. Quando è presente un indice di supporto, stai limitando il motore di archiviazione a una strategia di scansione dell’ordine dell’indice (seguendo l’elenco collegato all’indice). Non gli concedi una maggiore flessibilità come di solito quando l’ordine non ha importanza nella scelta tra una scansione dell’ordine dell’indice e una scansione dell’ordine di allocazione (basata su pagine IAM).

Un’idea che vale la pena provare è specificare una costante, come 1, nella clausola di ordine della finestra. Se supportato, speri che l’ottimizzatore sia abbastanza intelligente da rendersi conto che tutte le righe hanno lo stesso valore, quindi non esiste una reale pertinenza dell’ordine e quindi non è necessario forzare un ordinamento o una scansione dell’ordine dell’indice. Ecco una query che tenta questo approccio:

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

Sfortunatamente, SQL Server non supporta questa soluzione. Viene generato il seguente errore:

Msg 5308, livello 16, stato 1, riga 56
Le funzioni con finestra, le aggregazioni e le funzioni NEXT VALUE FOR non supportano gli indici interi come espressioni della clausola ORDER BY.

Apparentemente, SQL Server presume che se stai usando una costante intera nella clausola di ordine della finestra, rappresenta una posizione ordinale di un elemento nell’elenco SELECT, come quando specifichi un numero intero nella presentazione ORDER Clausola BY. In tal caso, un’altra opzione che vale la pena provare è specificare una costante non intera, in questo modo:

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

Risulta che anche questa soluzione non è supportata. SQL Server genera il seguente errore:

Msg 5309, livello 16, stato 1, riga 65
Le funzioni con finestra, le aggregazioni e le funzioni NEXT VALUE FOR non supportano le costanti come espressioni della clausola ORDER BY.

Apparentemente, la clausola di ordinamento delle finestre non supporta alcun tipo di costante.

Finora abbiamo imparato quanto segue sulla rilevanza dell’ordinamento delle finestre della funzione ROW_NUMBER in SQL Server:

  1. ORDER BY è obbligatorio.
  2. Impossibile ordinare per una costante intera poiché SQL Server pensa che si stia tentando di specificare una posizione ordinale in SELECT.
  3. Impossibile ordinare per qualsiasi tipo di costante.

La conclusione è che dovresti ordinare in base a espressioni che non sono costanti. Ovviamente, puoi ordinare in base a un elenco di colonne dalla tabella o dalle tabelle interrogate. Ma siamo alla ricerca di una soluzione efficiente in cui l’ottimizzatore possa rendersi conto che non c’è rilevanza nell’ordinamento.

Ripiegamento costante

La conclusione finora è che non è possibile utilizzare costanti in il ROW_NUMBER la clausola di ordine delle finestre, ma per quanto riguarda le espressioni basate su costanti, come nella seguente query:

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

Tuttavia, questo tentativo cade vittima di un processo noto come costante pieghevole, che normalmente ha un impatto positivo sulle prestazioni delle query. L’idea alla base di questa tecnica è di migliorare le prestazioni delle query piegando alcune espressioni basate su costanti alle loro costanti di risultato in una fase iniziale dell’elaborazione della query. Puoi trovare dettagli su quali tipi di espressioni possono essere piegate costantemente qui. La nostra espressione 1 + 0 viene piegata a 1, risultando nello stesso errore che hai ottenuto quando hai specificato direttamente la costante 1:

Msg 5308, livello 16, stato 1, riga 79
Funzioni con finestra, gli aggregati e le funzioni NEXT VALUE FOR non supportano indici interi come espressioni della clausola ORDER BY.

Dovresti affrontare una situazione simile quando tenti di concatenare due stringhe di caratteri letterali, in questo modo:

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

Viene visualizzato lo stesso errore che si verifica quando si specifica direttamente il valore letterale “Nessun ordine”:

Msg 5309, livello 16, stato 1, riga 55
con finestra le funzioni, gli aggregati e le funzioni NEXT VALUE FOR non supportano le costanti come espressioni della clausola ORDER BY.

Mondo bizzarro: errori che prevengono gli errori

La vita è piena di sorprese …

Una cosa che impedisce il ripiegamento costante è quando l’espressione normalmente restituisce un errore. Ad esempio, l’espressione 2147483646 + 1 può essere piegata con costanti poiché risulta in un valore di tipo INT valido. Di conseguenza, un tentativo di eseguire la seguente query non riesce:

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

Msg 5308, livello 16, stato 1, riga 109
Funzioni con finestra, aggregati e VALORE SUCCESSIVO Le funzioni FOR non supportano indici interi come espressioni della clausola ORDER BY.

Tuttavia, l’espressione 2147483647 + 1 non può essere piegata in modo costante perché un tale tentativo avrebbe provocato un errore di overflow INT. L’implicazione sull’ordinazione è piuttosto interessante. Prova la seguente query (la chiameremo Query 2):

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

Stranamente, questa query viene eseguita correttamente! Quello che succede è che da un lato SQL Server non riesce ad applicare la piegatura costante e quindi l’ordinamento si basa su un’espressione che non è una singola costante. D’altra parte, l’ottimizzatore calcola che il valore di ordinamento è lo stesso per tutte le righe, quindi ignora completamente l’espressione di ordinamento. Ciò è confermato quando si esamina il piano per questa query, come mostrato nella Figura 3.

Figura 3: Piano per la query 2

Osserva che il piano scansiona un indice di copertura con una proprietà Ordered: False. Questo era esattamente il nostro obiettivo di rendimento.

In modo simile, la seguente query implica un tentativo di ripiegamento costante riuscito e quindi non riesce:

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

Msg 5308, livello 16, stato 1, riga 123
Le funzioni con finestra, gli aggregati e le funzioni NEXT VALUE FOR non supportano gli indici interi come espressioni della clausola ORDER BY.

La seguente query implica un tentativo di ripiegamento costante fallito, e quindi riesce, generando il piano mostrato in precedenza nella Figura 3:

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

Quanto segue la query implica un tentativo di ripiegamento costante riuscito (il valore letterale VARCHAR “1” viene convertito implicitamente in INT 1, quindi 1 + 1 viene ripiegato in 2) e pertanto non riesce:

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

Msg 5308, livello 16, stato 1, riga 134
Le funzioni con finestra, gli aggregati e le funzioni NEXT VALUE FOR non supportano gli indici interi come espressioni della clausola ORDER BY.

La query seguente implica un tentativo di ripiegamento costante fallito (impossibile convertire “A” in INT), e quindi riesce, generando il piano mostrato in precedenza nella Figura 3:

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

Ad essere onesti, anche se questa tecnica bizzarra raggiunge il nostro obiettivo di prestazioni originale, non posso dire di considerarla sicura e quindi non mi sento così a mio agio nell’affidarci.

Costanti di runtime basate su funzioni

Continuando la ricerca di una buona soluzione per calcolare i numeri di riga con un ordine non deterministico, ci sono alcune tecniche che sembrano più sicure dell’ultima stravagante soluzione: usare costanti di runtime basate su funzioni, usare una sottoquery basata su una costante, usare una colonna con alias basata su una costante e utilizzando una variabile.

Come ho spiegato in T-SQL bug, insidie e best practice: il determinismo, la maggior parte delle funzioni in T-SQL vengono valutate solo una volta per riferimento nella query, non una per riga. Questo è il caso anche con la maggior parte delle funzioni non deterministiche come GETDATE e RAND. Ci sono pochissime eccezioni a questa regola, come le funzioni NEWID e CRYPT_GEN_RANDOM, che vengono valutate una volta per riga. La maggior parte delle funzioni, come GETDATE, @@ SPID e molte altre, vengono valutate una volta all’inizio della query ei relativi valori vengono quindi considerati costanti di runtime. Un riferimento a tali funzioni non viene ripiegato costantemente. Queste caratteristiche rendono una costante di runtime basata su una funzione una buona scelta come elemento di ordinamento delle finestre e, in effetti, sembra che T-SQL la supporti. Allo stesso tempo, l’ottimizzatore si rende conto che in pratica non esiste la pertinenza dell’ordine, evitando inutili penalizzazioni sulle prestazioni.

Ecco un esempio che utilizza la funzione GETDATE:

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

Questa query ottiene lo stesso piano mostrato in precedenza nella Figura 3.

Ecco un altro esempio che utilizza la funzione @@ SPID (che restituisce l’ID della sessione corrente):

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

E la funzione PI? Prova la seguente query:

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

Questo fallisce con il seguente errore:

Msg 5309, Livello 16, Stato 1, Riga 153
Le funzioni con finestra, gli aggregati e le funzioni NEXT VALUE FOR non supportano le costanti come espressioni della clausola ORDER BY.

Funzioni come GETDATE e @@ SPID vengono rivalutate una volta per esecuzione del piano, quindi non possono ottenere piegato costante. PI rappresenta sempre la stessa costante e quindi viene ripiegata.

Come accennato in precedenza, ci sono pochissime funzioni che vengono valutate una volta per riga, come NEWID e CRYPT_GEN_RANDOM. Questo li rende una cattiva scelta come elemento di ordinamento delle finestre se hai bisogno di un ordine non deterministico, da non confondere con l’ordine casuale. Perché pagare una penalità di ordinamento non necessaria?

Ecco un esempio che utilizza la funzione NEWID:

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

Il piano per questa query è mostrato nella Figura 4, a conferma che SQL Server ha aggiunto esplicitamente ordinamento in base al risultato della funzione.

Figura 4: Piano per query 3

Se si desidera assegnare i numeri di riga in ordine casuale, in ogni caso, questa è la tecnica che vuoi usare. Devi solo essere consapevole che incorre nel costo di ordinamento.

Uso di una sottoquery

Puoi anche usare una sottoquery basata su una costante come espressione di ordinamento delle finestre (ad esempio, ORDER BY (SELEZIONA “Nessun ordine”)). Inoltre, con questa soluzione, l’ottimizzatore di SQL Server riconosce l’assenza di rilevanza nell’ordinamento e quindi non impone un ordinamento non necessario né limita le scelte del motore di archiviazione a quelle che devono garantire l’ordine. Prova a eseguire la seguente query come esempio:

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

Ottieni lo stesso piano mostrato in precedenza nella Figura 3.

Uno dei grandi vantaggi di questa tecnica è che puoi aggiungere il tuo tocco personale.Forse ti piacciono davvero i NULL:

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

Forse ti piace davvero un certo numero:

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

Forse vuoi inviare un messaggio a qualcuno:

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

Hai capito.

Fattibile, ma scomodo

Ci sono un paio di tecniche che funzionano, ma sono un po ‘scomode. Uno consiste nel definire un alias di colonna per un’espressione basata su una costante, quindi utilizzare tale alias di colonna come elemento di ordinamento della finestra. Puoi farlo usando un’espressione di tabella o con l’operatore CROSS APPLY e un costruttore di valori di tabella. Ecco un esempio per quest’ultimo:

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

Ottieni lo stesso piano mostrato in precedenza nella Figura 3.

Un’altra opzione è usare una variabile come elemento di ordinamento delle finestre:

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

Questa query ottiene anche il piano mostrato in precedenza nella Figura 3.

E se utilizzo la mia UDF ?

Potresti pensare che usare la tua UDF che restituisce una costante potrebbe essere una buona scelta come elemento di ordinamento delle finestre quando vuoi un ordine non deterministico, ma non lo è. Considera la seguente definizione UDF come esempio:

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

Prova a usare l’UDF come clausola di ordinamento delle finestre, in questo modo (chiameremo questa Query 4):

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

Prima di SQL Server 2019 (o livello di compatibilità parallela < 150), le funzioni definite dall’utente vengono valutate per riga . Anche se restituiscono una costante, non vengono inline. Di conseguenza, da un lato è possibile utilizzare tale UDF come elemento di ordinamento delle finestre, ma dall’altro si traduce in una penalità di ordinamento. Ciò è confermato esaminando il piano per questa query, come mostrato nella Figura 5.

Figura 5: Piano per query 4

A partire da SQL Server 2019, con il livello di compatibilità > = 150, tali funzioni definite dall’utente vengono integrate, il che è per lo più un’ottima cosa, ma nel nostro caso si traduce in un errore:

Msg 5309, livello 16, stato 1, riga 217
Le funzioni con finestra, gli aggregati e le funzioni NEXT VALUE FOR non supportano le costanti come espressioni della clausola ORDER BY.

Quindi utilizzare un’UDF basata su una costante come elemento di ordinamento delle finestre forza un ordinamento o un errore a seconda della versione di SQL Server in uso e del livello di compatibilità del database. In breve, non farlo.

Numeri di riga partizionati con ordine non deterministico

Un caso d’uso comune per i numeri di riga partizionati basati su un ordine non deterministico è la restituzione di qualsiasi riga per gruppo. Dato che per definizione esiste un elemento di partizionamento in questo scenario, si potrebbe pensare che una tecnica sicura in tal caso sarebbe quella di utilizzare l’elemento di partizionamento delle finestre anche come elemento di ordinamento delle finestre. Come primo passaggio, calcoli i numeri di riga in questo modo:

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

Il piano per questa query è mostrato nella Figura 6.

Figura 6: Plan for Query 5

Il motivo per cui il nostro indice di supporto viene scansionato con una proprietà Ordered: True è perché SQL Server ha bisogno di elaborare le righe di ogni partizione come unità singola. Questo è il caso prima del filtraggio. Se si filtra solo una riga per partizione, si hanno come opzioni sia algoritmi basati su ordine che basati su hash.

Il secondo passaggio consiste nel posizionare la query con il calcolo del numero di riga in un’espressione di tabella e in la query esterna filtra la riga con la riga numero 1 in ogni partizione, in questo modo:

Teoricamente questa tecnica dovrebbe essere sicura, ma Paul White ha trovato un bug che mostra che usando questo metodo puoi ottenere attributi da righe di origine diverse nella riga dei risultati restituiti per partizione. Usare una costante di runtime basata su una funzione o una sottoquery basata su una costante poiché l’elemento di ordinamento sembra essere sicuro anche con questo scenario, quindi assicurati di utilizzare una soluzione come la seguente:

Nessuno deve passare in questo modo senza il mio permesso

Cercare di calcolare i numeri di riga in base a un ordine non deterministico è un’esigenza comune. Sarebbe stato carino se T-SQL rendesse semplicemente facoltativa la clausola di ordine della finestra per la funzione ROW_NUMBER, ma non lo fa. In caso contrario, sarebbe stato bello se almeno consentisse di utilizzare una costante come elemento di ordinamento, ma neanche questa è un’opzione supportata.Ma se chiedi gentilmente, sotto forma di una sottoquery basata su una costante o una costante di runtime basata su una funzione, SQL Server lo consentirà. Queste sono le due opzioni con cui mi trovo più a mio agio. Non mi sento davvero a mio agio con le strane espressioni errate che sembrano funzionare, quindi non posso consigliare questa opzione.

Leave a Reply

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *