Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

cte récursif avec fonctions de classement

MODIFIER

Lorsque vous lisez la documentation CTE concernant la récursivité, vous remarquerez qu'elle a certaines limites, telles que l'impossibilité d'utiliser des sous-requêtes, group-by, top. Ceux-ci impliquent tous plusieurs lignes. Des tests limités et la vérification du plan d'exécution, ainsi que le test de cette requête

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

Je ne peux que conclure :

  1. Row_Number() fonctionne dans un CTE, lorsque d'autres tables sont jointes pour produire un ensemble de résultats multi-lignes
  2. D'après les résultats de la numérotation, il est clair que les CTE sont traités sur une seule ligne à travers toutes les itérations, ligne par ligne au lieu de multiligne par multiligne, même s'il semble parcourir toutes les lignes simultanément. Cela expliquerait pourquoi l'une des fonctions qui s'appliquent aux opérations multi-lignes n'est pas autorisée pour le CTE récursif.

Bien que je sois arrivé à cette conclusion facilement, quelqu'un a évidemment pris beaucoup plus de temps pour expliquez-le avec des détails atroces uniquement Il y a 17 mois...

En d'autres termes, c'est la nature de l'implémentation de SQL Server CTE récursif, donc les fonctions de fenêtrage ne fonctionneront pas comme prévu.

Pour le bénéfice des autres, la sortie est :
a           b           c           d
----------- ----------- ----------- -----------
1           1           1           2
1           2           1           2
2           3           1           2
2           4           1           2

Alors que vous vous attendez à ce que c contienne 1,2,1,2 au lieu de 1,1,1,1. Cela semble certainement être un bogue, car il n'existe aucune documentation indiquant que les fonctions de fenêtrage ne devraient pas fonctionner dans la partie récursive d'un CTE.

Remarque :row_number() renvoie bigint, vous pouvez donc convertir uniquement l'ancre (c) en bigint.

Étant donné que chaque itération augmente d, vous pouvez effectuer le fenêtrage à l'extérieur.

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

MODIFIER - aperçu

En répondant à une autre question , j'ai joué un peu plus avec CTE récursif. Si vous l'exécutez sans le ORDER BY final, vous pouvez voir comment SQL Server aborde la récursivité. Il est intéressant de noter qu'il revient en arrière dans ce cas, puis effectue une récursivité complète en profondeur d'abord sur chaque ligne.

Exemple de tableau

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'

Une requête récursive

;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

La sortie montre l'ancre CTE traitée dans la première itération, puis pour une raison quelconque chaque ligne de l'ensemble d'ancrage est récursée jusqu'à l'achèvement (profondeur d'abord) avant de traiter les autres lignes.

Pourtant, il a ses utilisations étranges, comme cette réponse montre