开发者

Return hierarchy without creating any views or tables?

I have a table with columns and data as follows:

Table1
ID  Name    PID
A1  Apple   P1
B1  Book    A1
B2  开发者_StackOverflow中文版Brook   A1
C1  Cat     B1
C2  Cook    B1
C3  Car     B1
D1  Dog     B2
D2  Doll    B2
E1  Egg     C1

I want the results as follows:

ID  Name                    Depth
B1  Apple\Book              2
C1  Apple\Book\Cat          3
E1  Apple\Book\Cat\Egg      4
C2  Apple\Book\Cook         3
C3  Apple\Book\Car          3
B2  Apple\Brook             2
D1  Apple\Brook\Dog         3
D2  Apple\Brook\Doll        3

The relationship is that a row is a child of another row if PID of that row is equal to ID of the parent row.

Apple is the base. So the first statement would be something like:

Select ID, Name, 2 from Table1 where PID=(select ID from Table1 where Name='Apple');

My current solution is creating a lot of views and save all those similar statements like that to views and union them together. But I don't want that. I want to finish that within 1 select statement.A


I don't know if there's an elegant way to get the exact ordering you listed, but here is one approach with a recursive CTE:

;WITH cte AS
(
    SELECT ID, Name, Depth = 1
        FROM dbo.Table1 
        WHERE Name = 'Apple'
    UNION ALL
    SELECT t.ID, t.Name, Depth = cte.Depth + 1
        FROM cte 
        INNER JOIN dbo.Table1 AS t
        ON t.PID = cte.ID
)
SELECT ID, Name, Depth
FROM cte
WHERE Depth > 1;


declare @Table1 table
(
  ID varchar(2),
  Name varchar(10),
  PID varchar(2)
)

insert into @Table1 values  
('A1',  'Apple',   'P1'),
('B1',  'Book',    'A1'),
('B2',  'Brook',   'A1'),
('C1',  'Cat',     'B1'),
('C2',  'Cook',    'B1'),
('C3',  'Car',     'B1'),
('D1',  'Dog',     'B2'),
('D2',  'Doll',    'B2'),
('E1',  'Egg',     'C1')

;with C as
(
  select T.ID,
         cast(T.Name as varchar(max)) as Name,
         1 as Depth
  from @Table1 as T
  where T.Name = 'Apple'
  union all
  select T.ID,
         cast(C.Name+'\'+T.Name as varchar(max)),
         C.Depth + 1
  from @Table1 as T
    inner join C
      on T.PID = C.ID
)
select C.ID,
       C.Name,
       C.Depth
from C
where C.Depth > 1
order by C.Name

Edit With out the Apple.

;with C as
(
  select T.ID,
         cast(T.Name as varchar(max)) as Name,
         1 as Depth
  from @Table1 as T
    inner join @Table1 as TP
      on T.PID = TP.ID
  where TP.Name = 'Apple'
  union all
  select T.ID,
         cast(C.Name+'\'+T.Name as varchar(max)),
         C.Depth + 1
  from @Table1 as T
    inner join C
      on T.PID = C.ID
)
select C.ID,
       C.Name,
       C.Depth
from C
order by C.Name


Here is the query I came up with on PostgreSQL. I don't have SQL Server, so I can't test, but Google has led me to believe that this query structure will work on SQL Server as well.

SELECT t1.*, (
    WITH q AS (
        SELECT t2.*
        FROM Table1 AS t2
        WHERE t2.ID = t1.ID
           OR (t1.ID IS NULL AND t2.ID IS NULL)

        UNION ALL

        SELECT t3.*
        FROM Table1 AS t3
        JOIN q
        ON t3.ID = q.PID
        AND t3.ID <> 'P1'
        -- 'P1' on the above line is the "sentinel" value you want to
        -- stop traversing at.  Remove the AND clause altogether if you
        -- want to traverse up to the ultimate root record.
    )
    SELECT COUNT(q.ID)
    FROM q
) AS depth
FROM Table1 AS t1;

Here is an example run on PostgreSQL. Note that PostgreSQL requires WITH RECURSIVE when declaring a common table expression that references itself. This, and the sample data, are the only differences between the two queries:

$ WITH Table1(ID, Name, PID) AS (VALUES
$     ('A1', 'Apple', 'P1'),
$     ('B1', 'Book',  'A1'),
$     ('B2', 'Brook', 'A1'),
$     ('C1', 'Cat',   'B1'),
$     ('C2', 'Cook',  'B1'),
$     ('C3', 'Car',   'B1'),
$     ('D1', 'Dog',   'B2'),
$     ('D2', 'Doll',  'B2'),
$     ('E1', 'Egg',   'C1')
$ )
$ SELECT t1.*, (
$     WITH RECURSIVE q AS (
$         SELECT t2.*
$         FROM Table1 AS t2
$         WHERE t2.ID = t1.ID
$            OR (t1.ID IS NULL AND t2.ID IS NULL)
$
$         UNION ALL
$
$         SELECT t3.*
$         FROM Table1 AS t3
$         JOIN q
$         ON t3.ID = q.PID
$         AND t3.ID <> 'P1'
$     )
$     SELECT COUNT(q.ID)
$     FROM q
$ ) AS depth
$ FROM Table1 AS t1;
 id | name  | pid | depth
----+-------+-----+-------
 A1 | Apple | P1  |     1
 B1 | Book  | A1  |     2
 B2 | Brook | A1  |     2
 C1 | Cat   | B1  |     3
 C2 | Cook  | B1  |     3
 C3 | Car   | B1  |     3
 D1 | Dog   | B2  |     3
 D2 | Doll  | B2  |     3
 E1 | Egg   | C1  |     4
(9 rows)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜