¿La forma más sencilla de realizar una autounión recursiva?


100

¿Cuál es la forma más sencilla de realizar una autounión recursiva en SQL Server? Tengo una mesa como esta:

PersonID | Initials | ParentID
1          CJ         NULL
2          EB         1
3          MB         1
4          SW         2
5          YT         NULL
6          IS         5

Y quiero poder obtener los registros solo relacionados con una jerarquía que comience con una persona específica. Entonces, si solicitara la jerarquía de CJ por PersonID = 1, obtendría:

PersonID | Initials | ParentID
1          CJ         NULL
2          EB         1
3          MB         1
4          SW         2

Y para EB obtendría:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

Estoy un poco atascado en esto, no puedo pensar en cómo hacerlo aparte de una respuesta de profundidad fija basada en un montón de combinaciones. Esto serviría como sucede porque no tendremos muchos niveles, pero me gustaría hacerlo correctamente.

¡Gracias! Chris.


2
¿Qué versión de SQL Server estás usando? es decir, Sql 2000, 2005, 2008?
boydc7

2
SO preguntas sobre consultas recursivas: stackoverflow.com/search?q=sql-server+recursive
OMG Ponies

Respuestas:


112
WITH    q AS 
        (
        SELECT  *
        FROM    mytable
        WHERE   ParentID IS NULL -- this condition defines the ultimate ancestors in your chain, change it as appropriate
        UNION ALL
        SELECT  m.*
        FROM    mytable m
        JOIN    q
        ON      m.parentID = q.PersonID
        )
SELECT  *
FROM    q

Al agregar la condición de pedido, puede preservar el orden del árbol:

WITH    q AS 
        (
        SELECT  m.*, CAST(ROW_NUMBER() OVER (ORDER BY m.PersonId) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc
        FROM    mytable m
        WHERE   ParentID IS NULL
        UNION ALL
        SELECT  m.*,  q.bc + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.PersonID) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN
        FROM    mytable m
        JOIN    q
        ON      m.parentID = q.PersonID
        )
SELECT  *
FROM    q
ORDER BY
        bc

Al cambiar la ORDER BYcondición, puede cambiar el orden de los hermanos.


7
+1, excepto que Chris necesitaría en PersonID = theIdYouAreLookingForlugar de ParentID IS NULL.
Heinzi

He publicado una nueva pregunta en SO, stackoverflow.com/questions/13535003/…
Kishore Kumar

@Aaroninus: el nodo principal está definido por la consulta superior (ancla) en la WITHcláusula. Si necesita información específica, cree un violín en sqlfiddle.com y publique el enlace aquí.
Quassnoi

24

Usando CTE puede hacerlo de esta manera

DECLARE @Table TABLE(
        PersonID INT,
        Initials VARCHAR(20),
        ParentID INT
)

INSERT INTO @Table SELECT     1,'CJ',NULL
INSERT INTO @Table SELECT     2,'EB',1
INSERT INTO @Table SELECT     3,'MB',1
INSERT INTO @Table SELECT     4,'SW',2
INSERT INTO @Table SELECT     5,'YT',NULL
INSERT INTO @Table SELECT     6,'IS',5

DECLARE @PersonID INT

SELECT @PersonID = 1

;WITH Selects AS (
        SELECT *
        FROM    @Table
        WHERE   PersonID = @PersonID
        UNION ALL
        SELECT  t.*
        FROM    @Table t INNER JOIN
                Selects s ON t.ParentID = s.PersonID
)
SELECT  *
FROm    Selects

2
Buena respuesta completa con el importante DONDE PersonID = @PersonID
Oli B

5

La consulta de Quassnoi con un cambio para tabla grande. Padres con más hijos que 10: formateado como str (5) el número de fila ()

CON q COMO 
        (
        SELECT m. *, CAST (str (ROW_NUMBER () OVER (ORDER BY m.ordernum), 5) AS VARCHAR (MAX)) COLLATE Latin1_General_BIN AS bc
        DESDE #tm
        DONDE ParentID = 0
        UNIÓN TODOS
        SELECCIONE m. *, Q.bc + '.' + str (ROW_NUMBER () OVER (PARTICIÓN POR m.ParentID ORDER BY m.ordernum), 5) COLLATE Latin1_General_BIN
        DESDE #tm
        ÚNETE q
        ON m.parentID = q.DBID
        )
SELECCIONE *
DESDE q
PEDIR POR
        antes de Cristo


2

SQL 2005 o posterior, los CTE son el camino estándar a seguir según los ejemplos que se muestran.

SQL 2000, puede hacerlo usando UDF -

CREATE FUNCTION udfPersonAndChildren
(
    @PersonID int
)
RETURNS @t TABLE (personid int, initials nchar(10), parentid int null)
AS
begin
    insert into @t 
    select * from people p      
    where personID=@PersonID

    while @@rowcount > 0
    begin
      insert into @t 
      select p.*
      from people p
        inner join @t o on p.parentid=o.personid
        left join @t o2 on p.personid=o2.personid
      where o2.personid is null
    end

    return
end

(que funcionará en 2005, simplemente no es la forma estándar de hacerlo. Dicho esto, si encuentra que es la forma más fácil de trabajar, hágalo funcionar)

Si realmente necesita hacer esto en SQL7, puede hacer aproximadamente lo anterior en un sproc pero no pudo seleccionarlo; SQL7 no admite UDF.


2

Verifique lo siguiente para ayudar a comprender el concepto de recursividad CTE

DECLARE
@startDate DATETIME,
@endDate DATETIME

SET @startDate = '11/10/2011'
SET @endDate = '03/25/2012'

; WITH CTE AS (
    SELECT
        YEAR(@startDate) AS 'yr',
        MONTH(@startDate) AS 'mm',
        DATENAME(mm, @startDate) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        @startDate 'new_date'
    UNION ALL
    SELECT
        YEAR(new_date) AS 'yr',
        MONTH(new_date) AS 'mm',
        DATENAME(mm, new_date) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        DATEADD(d,1,new_date) 'new_date'
    FROM CTE
    WHERE new_date < @endDate
    )
SELECT yr AS 'Year', mon AS 'Month', count(dd) AS 'Days'
FROM CTE
GROUP BY mon, yr, mm
ORDER BY yr, mm
OPTION (MAXRECURSION 1000)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.