2009-11-18 16 views
83

¿Cuál es la forma más simple de hacer una auto-unión recursiva en SQL Server? Tengo una tabla como la siguiente:¿La manera más simple de hacer una auto-unión recursiva?

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

Y yo quiero ser capaz de obtener los registros solo acerca de una jerarquía a partir de una persona específica. Así que si yo solicité la jerarquía de CJ por PersonaID = 1 me gustaría tener:

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

Y para EB de que conseguiría:

PersonID | Initials | ParentID 
2   EB   1 
4   SW   2 

estoy un poco atascado en esto no se puede pensar en lo para hacerlo aparte de una respuesta de profundidad fija basada en un grupo de combinaciones. Esto funcionaría como sucede porque no tendremos muchos niveles, pero me gustaría hacerlo correctamente.

Gracias! Chris.

+2

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

+2

ASÍ QUE preguntas sobre consultas recursivas: http://stackoverflow.com/search?q=sql-server+recursive –

Respuesta

85
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 añadir la condición de pedidos, puede conservar 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 condición ORDER BY se puede cambiar el orden de los hermanos.

+6

+1, excepto que Chris necesitaría 'PersonID = theIdYouAreLookingFor' en lugar de' ParentID IS NULL'. – Heinzi

+0

Es posible obtener el mismo resultado utilizando EntityFramework –

+1

@TheIndianProgrammmer: es posible obtener el mismo resultado sin utilizar EntityFramework. – Quassnoi

18

Uso de CTE se puede hacer 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 
+0

Buena respuesta completa con el importante WHERE \t PersonID = @PersonID –

2

SQL 2005 o posterior, CTE son la forma estándar para ir de acuerdo con los ejemplos mostrados.

SQL 2000, puede hacerlo utilizando las 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 [email protected] 

    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, no es sólo la forma estándar de hacerlo Dicho esto, si usted encuentra que la forma más fácil de trabajar. , ejecute con él)

Si realmente necesita hacer esto en SQL7, puede hacer más o menos lo anterior en un sproc pero no pudo seleccionarlo - SQL7 no es compatible con UDF.

4

La consulta de Quassnoi con un cambio para la tabla grande. Padres con más hijos luego de 10: Formateando como str (5) row_number()

 
WITH q AS 
     (
     SELECT m.*, CAST(str(ROW_NUMBER() OVER (ORDER BY m.ordernum),5) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc 
     FROM #t m 
     WHERE ParentID =0 
     UNION ALL 
     SELECT m.*, q.bc + '.' + str(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.ordernum),5) COLLATE Latin1_General_BIN 
     FROM #t m 
     JOIN q 
     ON  m.parentID = q.DBID 
     ) 
SELECT * 
FROM q 
ORDER BY 
     bc 

Cuestiones relacionadas