2009-11-06 6 views
36

Hallo Ich habe eine Tabelle, die sich selbst verweist und ich muss in der Lage sein, den übergeordneten und alle untergeordneten Datensätze aus einer bestimmten übergeordneten ID auszuwählen.SQL Server: Wie alle untergeordneten Datensätze eine übergeordnete ID in einer sich selbst referenzierenden Tabelle erhalten

Meine Tabelle ist wie folgt:

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

So für das obige Beispiel würde Ich mag Lage sein, in einem Wert von 1 passieren und die Datensätze alle oben zu erhalten.

Bisher habe ich die folgende rekursive Tabellenwert-Funktion entwickelt, aber sie verhält sich nicht wie erwartet (nur der erste Datensatz wird zurückgegeben).

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 für den Versuch, sich selbst zu lösen, bevor Sie hier eine Frage stellen. – iMatoria

Antwort

52

können Sie diese versuchen

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 CTEs sind ideal für Rekursion, sie haben jedoch relativ kleine Grenzen für die Anzahl der Male, die sie sich nennen können. Wenn deine Verschachtelungsebenen wirklich tief gehen, dann pass auf. Ich denke, das Limit ist 100. –

+2

@Robin Day: Der Standardwert ist 100, Sie können es ändern, indem Sie "WITH MAXRECURSION number" am Ende der Abfrage anhängen. Die Zahl 0 bedeutet kein Limit. – Andomar

+1

MAXRECURSION Hinweis hat einen Wert zwischen 0 und 32.767 –

0

Es sei denn, Sie Oracle verwenden, ist Ihre Tabellenstruktur für das beschriebene Problem nicht geeignet. Was Sie versuchen, ist eine Hierarchie (durch eine Baumstruktur) zu greifen.

Es gibt einen Artikel, More Trees & Hierarchies in SQL, der eine Methode zur Lösung des Hierarchieproblems beschreibt. Er fügt im Grunde eine "Abstammung" -Spalte hinzu, die die Hierarchie jeder Zeile beschreibt.

+1

Die Frage sagt, er verwendet SQL Server 2005;) – Andomar

+1

@Abtin Leider arbeite ich mit einem Altsystem, so kann das Datenbankschema überhaupt nicht ändern :( –

+1

Nun, Rekursion FTW. –

0

Rekursion in CTE sieht etwas teuer aus, so habe ich diese Funktion geschrieben, die rekursiven Funktionsaufruf verwenden, aber viel schneller, dass CTE Rekursion.

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 
Verwandte Themen