2009-11-06 9 views
36

Ciao Ho una tabella che fa riferimento a se stessa e devo essere in grado di selezionare il genitore e tutti i suoi record figlio da un determinato ID padre.SQL Server: come ottenere tutti i record figlio dati un id padre in una tabella autoreferenziale

Il mio tavolo è la seguente:

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

Così, per l'esempio precedente mi piacerebbe essere in grado di passare in un valore di 1 e ottenere tutti i record di cui sopra.

Finora, ho elaborato la seguente funzione di valore di tabella ricorsiva, ma non si comporta come previsto (solo restituendo il primo record).

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 per cercare di risolvere se stessi, prima di fare domande qui. – iMatoria

risposta

52

si può provare questo

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 I CTE sono ottimi per la ricorsione, ma hanno limiti relativamente bassi sul numero di volte in cui possono chiamarsi. Se i livelli di nidificazione sono davvero profondi, fai attenzione. Penso che il limite sia 100. –

+2

@Robin Day: il valore predefinito è 100, è possibile modificarlo aggiungendo "WITH MAXRECURSION number" alla fine della query. Il numero 0 significa nessun limite. – Andomar

+1

Il suggerimento MAXRECURSION ha un valore compreso tra 0 e 32.767 –

0

A meno che non si sta utilizzando Oracle, la vostra struttura della tabella non è adatto per il problema descritto. Quello che stai tentando di fare è afferrare una gerarchia (attraversando una struttura ad albero).

C'è un articolo, More Trees & Hierarchies in SQL, che descrive un metodo per risolvere il problema della gerarchia. In pratica aggiunge una colonna di "lignaggio" che descrive la gerarchia in ogni riga.

+1

La domanda dice che sta usando SQL Server 2005;) – Andomar

+1

@Abtin Sfortunatamente sto lavorando con un sistema legacy quindi non posso modificare lo schema del database affatto :( –

+1

Bene allora, ricorsione FTW. –

0

La ricorsione in CTE sembra un po 'dispendiosa, quindi ho scritto questa funzione che utilizza la chiamata di funzione ricorsiva ma molto più veloce della ricorsione 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