2009-11-06 25 views
36

Hola Tengo una tabla que hace referencia a sí misma y necesito poder seleccionar el padre y todos sus registros secundarios de un ID padre dado.Servidor SQL: Cómo obtener todos los registros secundarios dado un id principal en una tabla autorreferencial

Mi tabla es la siguiente:

ID | ParentID | Name   
-----------------------    
1  NULL  A 
2  1   B-1 
3  1   B-2 
4  2   C-1 
5  2   C-2 

lo tanto, para el ejemplo anterior me gustaría ser capaz de pasar de un valor de 1 y obtener todos los registros anteriores.

Hasta ahora, he encontrado la siguiente función recursiva con valores de tabla, pero no se comporta como se esperaba (solo devuelve el primer registro).

CREATE FUNCTION [dbo].[SelectBranches] 
( 
    @id INT 
    ,@parentId INT 
) 
RETURNS @branchTable TABLE 
(
    ID INT 
    ,ParentID INT 
    ,Name INT 
) 
AS 
BEGIN 

    IF @branchId IS NOT NULL BEGIN 

     INSERT INTO @branchTable 
     SELECT 
      ID 
      ,ParentID 
      ,Name 
     FROM 
      tblLinkAdvertiserCity 
     WHERE 
      ID = @id 

    END 

    INSERT INTO @branchTable 
    SELECT 
     br.ID 
     ,br.ParentID 
     ,br.Name 
    FROM 
     @branchTable b 
    CROSS APPLY 
     dbo.SelectBranches(NULL, b.ParentID) br 

    RETURN 
END 
GO 
+2

+1 por intentar solucionarlo usted mismo, antes de hacer una pregunta aquí. – iMatoria

Respuesta

52

puede probar esta

DECLARE @Table TABLE(
     ID INT, 
     ParentID INT, 
     NAME VARCHAR(20) 
) 

INSERT INTO @Table (ID,ParentID,[NAME]) SELECT 1, NULL, 'A' 
INSERT INTO @Table (ID,ParentID,[NAME]) SELECT 2, 1, 'B-1' 
INSERT INTO @Table (ID,ParentID,[NAME]) SELECT 3, 1, 'B-2' 
INSERT INTO @Table (ID,ParentID,[NAME]) SELECT 4, 2, 'C-1' 
INSERT INTO @Table (ID,ParentID,[NAME]) SELECT 5, 2, 'C-2' 


DECLARE @ID INT 

SELECT @ID = 2 

;WITH ret AS(
     SELECT * 
     FROM @Table 
     WHERE ID = @ID 
     UNION ALL 
     SELECT t.* 
     FROM @Table t INNER JOIN 
       ret r ON t.ParentID = r.ID 
) 

SELECT * 
FROM ret 
+0

+1 Los CTE son excelentes para la recursión, pero tienen límites relativamente pequeños sobre la cantidad de veces que pueden llamarse a sí mismos. Si tus niveles de anidación son realmente profundos, ten cuidado. Creo que el límite es 100. –

+2

@Robin Day: el valor predeterminado es 100, puede cambiarlo agregando "CON MAXRECURSION number" al final de la consulta. El número 0 significa sin límite. – Andomar

+1

La sugerencia de MAXRECURSION tiene un valor entre 0 y 32.767 –

0

A menos que usted está utilizando Oracle, su estructura de la tabla no es adecuado para el problema descrito. Lo que intenta hacer es tomar una jerarquía (atravesando una estructura de árbol).

Hay un artículo, More Trees & Hierarchies in SQL, que describe un método para resolver el problema de la jerarquía. Básicamente agrega una columna de "linaje" que describe la jerarquía de cada fila.

+1

La pregunta dice que está usando SQL Server 2005;) – Andomar

+1

@Abtin Lamentablemente, estoy trabajando con un sistema heredado, por lo que no puedo cambiar el esquema de la base de datos :( –

+1

Bien, recursividad FTW. –

0

Recursividad en CTE parece un poco caro, así que he escrito esta función que hace uso de llamada a función recursiva pero mucho más rápido que la recursión CTE.

CREATE FUNCTION [dbo].[Fn_GetSubCategories] 
(
@p_ParentCategoryId INT 
) RETURNS @ResultTable TABLE 
( 
    Id INT 
) 
AS 
BEGIN 
--Insert first level subcategories. 
INSERT INTO @ResultTable 
SELECT Id FROM Category WHERE ParentCategoryId = @p_ParentCategoryId OR Id = @p_ParentCategoryId 

DECLARE @Id INT 
DECLARE @ParentCategory TABLE(Id INT) 

DECLARE cur_categories CURSOR 
LOCAL STATIC READ_ONLY FORWARD_ONLY FOR 
SELECT Id FROM Category WHERE ParentCategoryId = @p_ParentCategoryId and Id != @p_ParentCategoryId 
OPEN cur_categories 
IF @@CURSOR_ROWS > 0 
    BEGIN 
    FETCH NEXT FROM cur_categories INTO @Id 
    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     --Insert remaining level sub categories. 
     IF EXISTS(SELECT 1 FROM Category WHERE ParentCategoryId = @Id AND Id != @Id) 
     BEGIN 
      INSERT INTO @ResultTable 
      SELECT DISTINCT C.Id from Fn_GetSubCategories(@Id) C INNER JOIN @ResultTable R ON C.Id != R.Id 
     END 

    FETCH NEXT FROM cur_categories INTO @Id 
    END 

    --Delete duplicate records 
    ;WITH CTE AS 
    (SELECT *,ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Id) AS RN FROM @ResultTable) 
    DELETE FROM CTE WHERE RN<>1 

END 
CLOSE cur_categories 
DEALLOCATE cur_categories 

RETURN 

END 
Cuestiones relacionadas