简体   繁体   中英

How to use recursive logic to return only the Root row in a sql table (SQL Server 2008 R2)

This is for SQL Server 2008 R2, I'm a novice at SQL so please be as specific as you can.

Table1 has some recursive structure built into it, where the ParentId is either Null meaning it's the root, or ParentId is the Id of another row in Table1 which denotes it as a child.

Example data set:

Table1Id    ParentId
--------------------------------------------
1           NULL
2           1
3           1
4           2
5           NULL
6           2
7           6
8           NULL
9           8

With the above example the table then has the following tree structure with 3 root nodes:

Root                    1                 5                 8
Child(teir1)        2       3                               9
Child(teir2)      4   6
Child(tier3)          7
....

Is there a way to return only the Root row given any of the row Ids? For example:

InputId      ReturnedRowId
----------------------------
1             1
2             1
3             1
4             1
5             5
6             1
7             1
8             8
9             8

Any help would be appreciated.

You can use a CTE and traverse the hierarchy

    IF OBJECT_ID('tempdb..#testData') IS NOT NULL
        DROP TABLE #testData
    CREATE TABLE #testData (  
         Table1Id           INT
        ,ParentId           INT NULL
    )

    INSERT INTO #testData ( Table1Id, ParentId )
    VALUES
         (1, NULL )
        ,(2, 1 )
        ,(3, 1 )
        ,(4, 2 )
        ,(5, NULL )
        ,(6, 2 )
        ,(7, 6 )
        ,(8, NULL )
        ,(9, 8 )


    DECLARE @InputId INT

    SET @InputId = 2  --<<--Change this as appropriate

    ;WITH cteTraverse
    AS
    (
        SELECT
            T.Table1Id, T.ParentId
        FROM
            #testData T
        WHERE
            Table1Id = @InputId
        UNION ALL
        SELECT
            T1.Table1Id, T1.ParentId
        FROM
            #testData T1
        INNER JOIN
            cteTraverse T2 ON T1.Table1Id = T2.ParentId 
    )
    SELECT
        @InputId '@InputId', Table1Id 'ReturnedRowId'
    FROM
        cteTraverse
    WHERE
        ParentId IS NULL

This query do the job.

with CTE as 
(
    Select Table1ID as ID, Table1ID as Ancestor, 0 as level
    from Table1

    UNION ALL

    Select ID, ParentID, level + 1
    from Table1
        inner join CTE on CTE.Ancestor = Table1.Table1ID
    where ParentID is not NULL
)
,
R_only as 
(
Select ID as ID, MAX(level) as max_level 
from CTE 
group by ID
)

select CTE.ID, Ancestor 
from CTE inner join R_only on CTE.ID = R_only.ID and CTE.level = R_only.max_level
order by CTE.ID

Here's a script that finds the root nodes for the nodes table you have. What it does:

  • In the first CTE, recurses the nodes until the parent is found. Recursion keeps track of the depth in column level .
  • In a second CTE, determines the maximum depth for each node: max_level . This is the depth where the parent was determined.
  • Select the nodes with maximum depth.
CREATE TABLE #tree(table1_id INT PRIMARY KEY,parent_id INT);
INSERT INTO #tree(table1_id,parent_id)VALUES
    (1,NULL),(2,1),(3,1),(4,2),(5,NULL),(6,2),(7,6),(8,NULL),(9,8);

;WITH cte_tr AS (
    SELECT table1_id, parent_id, level=0
    FROM #tree
    UNION ALL
    SELECT t_c.table1_id, t_p.parent_id, level=t_c.level+1
    FROM cte_tr AS t_c
         INNER JOIN #tree AS t_p ON
             t_p.table1_id=t_c.parent_id
    WHERE t_p.parent_id IS NOT NULL
),
cte_ml AS (
    SELECT table1_id, max_level=MAX(level)
    FROM cte_tr
    GROUP BY table1_id
)
SELECT cte_tr.table1_id, root_node=ISNULL(cte_tr.parent_id,cte_tr.table1_id)
FROM cte_tr 
     INNER JOIN cte_ml ON 
         cte_ml.table1_id=cte_tr.table1_id AND 
         cte_ml.max_level=cte_tr.level
ORDER BY cte_tr.table1_id

DROP TABLE #tree;

Result:

+-----------+-----------+
| table1_id | root_node |
+-----------+-----------+
|         1 |         1 |
|         2 |         1 |
|         3 |         1 |
|         4 |         1 |
|         5 |         5 |
|         6 |         1 |
|         7 |         1 |
|         8 |         8 |
|         9 |         8 |
+-----------+-----------+

Use start with and connect by prior. Check this documentation here

http://psoug.org/reference/connectby.html

I am not sure if "connect by" is available in other databases other than Oracle. Check it out.

You could also try with clause. Someone has tried here.

Simulation of CONNECT BY PRIOR of ORACLE in SQL SERVER

With works somewhat like this

with query1 as (select .... from .... where ....),
     query2 as (select .... from .... where ....)
select ...
  from query1 q1,
       query2 q2
 where q1.xxxxx = q2.xxxx

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM