Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

cte ricorsivo con funzioni di ranking

MODIFICA

Quando leggi la documentazione CTE relativa alla ricorsione, noterai che ha alcuni limiti, come non essere in grado di utilizzare sottoquery, group-by, top. Tutti questi coinvolgono più righe. Da test limitati e controllo del piano di esecuzione, oltre a testare questa query

with cte as (
  select 1 a, 1 b union all select 1, 2 union all select 1, 3 union all select 2, 4
)
, rcte (a, b, c, d) as (
  select a, b, cast(0 as int), 1 
  from cte
  union all
  select r.a, cte.b, cast(ROW_NUMBER() over (order by r.b) as int), r.d+1
  from rcte r inner join cte on cte.a=r.a
  where r.d < 2
)
select * 
from rcte
where d=2
order by a, b

Posso solo concludere:

  1. Row_Number() funziona in un CTE, quando altre tabelle vengono unite per produrre un set di risultati a più righe
  2. Dai risultati della numerazione, è chiaro che i CTE vengono elaborati in una singola riga attraverso tutte le iterazioni, riga per riga anziché multiriga per multiriga, anche se sembra scorrere tutte le righe contemporaneamente. Questo spiegherebbe perché nessuna delle funzioni che si applicano alle operazioni su più righe non sono consentite per CTE ricorsive.

Anche se sono giunto facilmente a questa conclusione, qualcuno ovviamente ha impiegato molto più tempo per spiegalo in dettagli strazianti solo 17 mesi fa...

In altre parole, questa è la natura dell'implementazione di SQL Server CTE ricorsivo, quindi le funzioni di windowing non funzioneranno nel modo previsto.

A beneficio degli altri, l'output è:
a           b           c           d
----------- ----------- ----------- -----------
1           1           1           2
1           2           1           2
2           3           1           2
2           4           1           2

Mentre ti aspetti che c contenga 1,2,1,2 invece di 1,1,1,1. Sembra sicuramente un bug, dal momento che non c'è documentazione che dica che le funzioni di windowing non dovrebbero funzionare nella parte ricorsiva di un CTE.

Nota:row_number() restituisce bigint, quindi puoi lanciare solo anchor(c) come bigint.

Poiché ogni iterazione aumenta d, potresti eseguire il windowing all'esterno.

with cte as (
  select 1 a, 1 b union all select 1, 2 union all select 2, 3 union all select 2, 4
)
, rcte (a, b, d) as (
  select a, b, 1 
  from cte
  union all
  select a, b, d+1
  from rcte
  where d < 2
)
select a,b, ROW_NUMBER() over (partition by a,d order by b) c,d
from rcte
--where d=2
order by d, a, b

MODIFICA - approfondimento

Rispondendo a un'altra domanda , ho giocato di più con CTE ricorsivo. Se lo esegui senza l'ORDER BY finale, puoi vedere come SQL Server si sta avvicinando alla ricorsione. È interessante notare che in questo caso va indietro, quindi esegue una ricorsione in profondità completa su ogni riga.

Tabella di esempio

create table Testdata(SomeID int, OtherID int, Data varchar(max))
insert Testdata select 1, 9, '18,20,22,alpha,beta,gamma,delta'
insert Testdata select 2, 6, ''
insert Testdata select 3, 8, '11,12,.'
insert Testdata select 4, 7, '13,19,20,66,12,232,1232,12312,1312,abc,def'
insert Testdata select 5, 8, '17,19'

Una query ricorsiva

;with tmp(SomeID, OtherID, DataItem, Data) as (
select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
    STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from Testdata
union all
select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
    STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from tmp
where Data > ''
)
select SomeID, OtherID, DataItem, Data
from tmp
-- order by SomeID

L'output mostra l'ancora CTE elaborata nell'iterazione uno, quindi per qualsiasi motivo ogni riga nel set di ancoraggio viene ripetuta fino al completamento (prima della profondità) prima di elaborare le altre righe.

Eppure ha i suoi strani usi, come questa risposta spettacoli