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

Sådan finder du alle forbundne undergrafer i en urettet graf

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 .



  1. En introduktion til MySQL-implementering ved hjælp af en Ansible-rolle

  2. Tips til justering af postgreSQL-ydelse

  3. Hvordan søger man i flere kolonner i MySQL?

  4. Hvad er den bedste måde at samle databaseindsættelser på fra c#?