Her er en variant, der ikke bruger markør, men bruger en enkelt rekursiv forespørgsel.
Grundlæggende behandler den dataene som kanter i en graf og krydser rekursivt alle kanter af grafen og stopper, når sløjfen detekteres. Derefter lægger den alle fundne sløjfer i grupper og giver hver gruppe et nummer.
Se de detaljerede forklaringer på, hvordan det virker nedenfor. Jeg anbefaler dig at køre forespørgslen CTE-for-CTE og undersøge hvert mellemresultat for at forstå, hvad det gør.
Eksempel 1
DECLARE @T TABLE (ID int, Ident1 char(1), Ident2 char(1));INSERT INTO @T (ID, Ident1, Ident2) VALUES(1, 'a', 'a') ,(2, 'b', 'b'),(3, 'c', 'a'),(4, 'c', 'b'),(5, 'c', 'c');
Eksempel 2
Jeg tilføjede en række mere med z
værdi for at have flere rækker med uparrede værdier.
DECLARE @T TABLE (ID int, Ident1 char(1), Ident2 char(1));INSERT INTO @T (ID, Ident1, Ident2) VALUES(1, 'a', 'a') ,(1, 'a', 'c'),(2, 'b', 'f'),(3, 'a', 'g'),(4, 'c', 'h'),( 5, 'b', 'j'),(6, 'd', 'f'),(7, 'e', 'k'),(8, 'i', NULL),(88, 'z ', 'z'),(9, 'l', 'h');
Eksempel 3
DECLARE @T TABLE (ID int, Ident1 char(1), Ident2 char(1));INSERT INTO @T (ID, Ident1, Ident2) VALUES(1, 'a', 'f') ,(2, 'a', 'g'),(3, 'a', NULL),(4, 'b', 'c'),(5, 'b', 'a'),(6, 'b', 'h'),(7, 'b', 'j'),(8, 'b', NULL),(9, 'b', NULL),(10, 'b', 'g '),(11, 'c', 'k'),(12, 'c', 'b'),(13, 'd', 'l'),(14, 'd', 'f') ,(15, 'd', 'g'),(16, 'd', 'm'),(17, 'd', 'a'),(18, 'd', NULL),(19, 'd', 'a'),(20, 'e', 'c'),(21, 'e', 'b'),(22, 'e', NULL);
Forespørgsel
WITHCTE_IdentsAS( SELECT Ident1 AS Ident FROM @T UNION SELECT Ident2 AS Ident FROM @T),CTE_PairsAS( SELECT Ident1, Ident2 FROM @T WHERE Ident1 <> Ident2 UNION SELECT Ident2 AS Ident1, Ident1 AS Ident2 FROM @ T WHERE Ident1 <> Ident2),CTE_RecursiveAS( SELECT CAST(CTE_Idents.Ident AS varchar(8000)) AS AnchorIdent , Ident1 , Ident2 , CAST(',' + Ident1 + ',' + Ident2 + ',' AS varchar(8000 )) AS IdentPath , 1 AS Lvl FRA CTE_Pairs INNER JOIN CTE_Idents ON CTE_Idents.Ident =CTE_Pairs.Ident1 UNION ALL SELECT CTE_Recursive.AnchorIdent , CTE_Pairs.Ident1 , CTE_CIdent.Pairt. varchar(8000)) AS IdentPath , CTE_Recursive.Lvl + 1 AS Lvl FRA CTE_Pairs INNER JOIN CTE_Recursive PÅ CTE_Recursive.Ident2 =CTE_Pairs.Ident1 WHERE CTE_Recursive.IdentPath CTE(_% LIKE,' CTE_% Pairs.Ident2 + ',%' AS varchar(8000))),CTE_RecursionResultAS( SELECT AnchorIdent, Ident1, Ident2 FROM CTE_Recursive),CTE_CleanResultAS( SELECT AnchorIdent, Ident1 AS Ident FROM CTE_RecursionResult UNION FROM CTE_RecursionResult UNION FROM, CSELECT_Anchor AS IdentIdenResults) .Ident ,CASE NÅR CA_Data.XML_Value ER NULL SÅ CTE_Idents.Ident ELSE CA_Data.XML_Value END AS GroupMembers ,DENSE_RANK() OVER(ORDER BY CASE, NÅR CA_Data.XML_Value.XML_Value ER CTE_NULL.CTE_NULL.CTE_NULL.CTE_NULL.Dent.DentS Group) APPLY ( SELECT CTE_CleanResult.Ident+',' FROM CTE_CleanResult WHERE CTE_CleanResult.AnchorIdent =CTE_Idents.Ident ORDER BY CTE_CleanResult.Ident FOR XML PATH(''), TYPE ) AS CA_XML(XML_CAMLESS_Value.emlValue) .', 'NVARCHAR(MAX)') ) AS CA_Data(XML_Value)WHERE CTE_Idents.Ident IKKE ER NULLORDER AF Ident;
Resultat 1
+-------+---------------+--------+| Ident | Gruppemedlemmer | Gruppe-ID |+-------+--------------+--------+| en | a,b,c, | 1 || b | a,b,c, | 1 || c | a,b,c, | 1 |+-------+--------------------+--------+
Resultat 2
+-------+---------------+--------+| Ident | Gruppemedlemmer | Gruppe-ID |+-------+--------------+--------+| en | a,c,g,h,l, | 1 || b | b,d,f,j, | 2 || c | a,c,g,h,l, | 1 || d | b,d,f,j, | 2 || e | e,k, | 3 || f | b,d,f,j, | 2 || g | a,c,g,h,l, | 1 || h | a,c,g,h,l, | 1 || jeg | jeg | 4 || j | b,d,f,j, | 2 || k | e,k, | 3 || l | a,c,g,h,l, | 1 || z | z | 5 |+-------+--------------+--------+
Resultat 3
+------+--------------------------------+-------- -+| Ident | Gruppemedlemmer | Gruppe-ID |+-------+--------------------------------+--------+| en | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || b | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || c | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || d | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || e | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || f | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || g | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || h | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || j | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || k | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || l | a,b,c,d,e,f,g,h,j,k,l,m, | 1 || m | a,b,c,d,e,f,g,h,j,k,l,m, | 1 |+-------+--------------------------------+--------+ Sådan virker det
Jeg vil bruge det andet sæt eksempeldata til denne forklaring.
CTE_Idents
CTE_Idents
giver listen over alle identifikatorer, der vises i både Ident1
og Ident2
kolonner. Da de kan vises i enhver rækkefølge, har vi UNION
begge kolonner sammen. UNION
fjerner også eventuelle dubletter.
+-------+| Ident |+-------+| NULL || en || b || c || d || e || f || g || h || i || j || k || l || z |+-------+
CTE_Pairs
CTE_Pairs
giver listen over alle kanter af grafen i begge retninger. Igen, UNION
bruges til at fjerne eventuelle dubletter.
+--------+--------+| Ident1 | Ident2 |+--------+--------+| en | c || en | g || b | f || b | j || c | en || c | h || d | f || e | k || f | b || f | d || g | en || h | c || h | l || j | b || k | e || l | h |+--------+--------+
CTE_Recursive
CTE_Recursive
er hoveddelen af forespørgslen, der rekursivt krydser grafen startende fra hver unik identifikator. Disse startrækker er produceret af den første del af UNION ALL
.Den anden del af UNION ALL
slutter sig rekursivt til sig selv og forbinder Ident2
til Ident1
.Da vi har lavet CTE_Pairs
på forhånd med alle kanter skrevet i begge retninger, kan vi altid kun linke Ident2
til Ident1
og vi får alle stier i grafen. Samtidig bygger forespørgslen IdentPath
- en streng af kommaseparerede identifikatorer, som er blevet krydset indtil videre. Den bruges i WHERE
filter:
CTE_Recursive.IdentPath IKKE LIKE CAST('%,' + CTE_Pairs.Ident2 + ',%' AS varchar(8000))
Så snart vi støder på den identifikator, der var inkluderet i stien før, stopper rekursionen, da listen over tilsluttede noder er udtømt.AnchorIdent
er start-id'et for rekursionen, vil det senere blive brugt til at gruppere resultater.Lvl
er ikke rigtig brugt, jeg inkluderede det for bedre at forstå, hvad der foregår.
+-------------+--------+--------+----------- --+-----+| AnchorIdent | Ident1 | Ident2 | IdentPath | Lvl |+------------+--------+--------+-------------+- ----+| en | en | c | ,a,c, | 1 || en | en | g | ,a,g, | 1 || b | b | f | ,b,f, | 1 || b | b | j | ,b,j, | 1 || c | c | en | ,c,a, | 1 || c | c | h | ,c,h, | 1 || d | d | f | ,d,f, | 1 || e | e | k | ,e,k, | 1 || f | f | b | ,f,b, | 1 || f | f | d | ,f,d, | 1 || g | g | en | ,g,a, | 1 || h | h | c | ,h,c, | 1 || h | h | l | ,h,l, | 1 || j | j | b | ,j,b, | 1 || k | k | e | ,k,e, | 1 || l | l | h | ,l,h, | 1 || l | h | c | ,l,h,c, | 2 || l | c | en | ,l,h,c,a, | 3 || l | en | g | ,l,h,c,a,g, | 4 || j | b | f | ,j,b,f, | 2 || j | f | d | ,j,b,f,d, | 3 || h | c | en | ,h,c,a, | 2 || h | en | g | ,h,c,a,g, | 3 || g | en | c | ,g,a,c, | 2 || g | c | h | ,g,a,c,h, | 3 || g | h | l | ,g,a,c,h,l, | 4 || f | b | j | ,f,b,j, | 2 || d | f | b | ,d,f,b, | 2 || d | b | j | ,d,f,b,j, | 3 || c | h | l | ,c,h,l, | 2 || c | en | g | ,c,a,g, | 2 || b | f | d | ,b,f,d, | 2 || en | c | h | ,a,c,h, | 2 || en | h | l | ,a,c,h,l, | 3 |+-------------+--------+--------+----------- ----+
CTE_CleanResult
CTE_CleanResult
efterlader kun relevante dele fra CTE_Recursive
og sammenfletter igen begge Ident1
og Ident2
ved hjælp af UNION
.
+-------------+-------+| AnchorIdent | Ident |+--------------+-------+| en | en || en | c || en | g || en | h || en | l || b | b || b | d || b | f || b | j || c | en || c | c || c | g || c | h || c | l || d | b || d | d || d | f || d | j || e | e || e | k || f | b || f | d || f | f || f | j || g | en || g | c || g | g || g | h || g | l || h | en || h | c || h | g || h | h || h | l || j | b || j | d || j | f || j | j || k | e || k | k || l | en || l | c || l | g || l | h || l | l |+-------------+-------+
Endelig VALG
Nu skal vi bygge en streng af kommasepareret Ident
værdier for hver AnchorIdent
.KRYDSANVEND
med FOR XML
gør det.DENSE_RANK()
beregner GruppeID
numre for hver AnchorIdent
.