Sql Server 2014 - Deep Recursive Parent-Child Self Join - sql

I am trying to build a deep recursive self-join query. Having the table like:
Id | ParentId
1 | NULL
2 | 1
3 | 1
4 | 2
5 | 3
6 | 8
7 | 9
For Id 1 my query should be fetching 1,2,3,4,5 since they are either the children of 1 or children of the children of 1. In the given example 6 and 7 should not be included in the query result.
I tried using CTE but I am getting tons of duplicates:
WITH CTE AS (
SELECT Id, ParentId
FROM dbo.Table
WHERE ParentId IS NULL
UNION ALL
SELECT t.Id, t.ParentId
FROM dbo.Table t
INNER JOIN CTE c ON t.ParentId = c.Id
)
SELECT * FROM CTE
Ideas?

You can try to use DISTINCT to filter duplicate rows.
;WITH CTE AS (
SELECT Id, ParentId
FROM T
WHERE ParentId IS NULL
UNION ALL
SELECT t.Id, t.ParentId
FROM T
INNER JOIN CTE c ON t.ParentId = c.Id
)
SELECT DISTINCT Id, ParentId
FROM CTE

Try the following query with CTE where you can set parentId by #parentID:
DECLARE #parentID INT = 1
;WITH cte AS
(
SELECT
t.ID
, t.ParentId
FROM #table t
),
cteParent AS
(
SELECT
t.ID
, t.ParentId
FROM #table t
WHERE t.ParentId IN (SELECT t1.ID FROM #table t1 WHERE T1.ParentId = #parentID)
)
SELECT
DISTINCT c1.ID
, c1.ParentId
FROM cte c1
INNER JOIN cte c2 ON c2.ParentId = c1.ID
UNION ALL
SELECT *
FROM cteParent
And the sample data:
DECLARE #table TABLE
(
ID INT
, ParentId INT
)
INSERT INTO #table
(
ID,
ParentId
)
VALUES
(1, NULL )
, (2, 1 )
, (3, 1 )
, (4, 2 )
, (5, 3 )
, (6, 8 )
, (7, 9 )
OUTPUT:
ID ParentId
1 NULL
2 1
3 1
4 2
5 3

I don't see duplicates.
Your code returns the following on the data you provided:
Id ParentId
1
2 1
3 1
5 3
4 2
which is what you want.
Here is a db<>fiddle.
Here is the code:
WITH t as (
SELECT *
FROM (VALUES (1, NULL), (2, 1), (3, 1), (4, 2), (5, 3), (6, 8), (7, 9)
) v(id, parentId)
),
CTE AS (
SELECT Id, ParentId
FROM t
WHERE ParentId IS NULL
UNION ALL
SELECT t.Id, t.ParentId
FROM t
INNER JOIN CTE c ON t.ParentId = c.Id
)
SELECT *
FROM CTE;
If you are getting duplicates in your actual result set, then you presumably have duplicates in your original table. I would recommend removing them before doing the recursive logic:
with t as (
select distinct id, parentid
from <your query>
),
. . .
Then run the recursive logic.

Try this sql script which result Parent Child Hierarchy
;WITH CTE(Id , ParentId)
AS
(
SELECT 1 , NULL UNION ALL
SELECT 2 , 1 UNION ALL
SELECT 3 , 1 UNION ALL
SELECT 4 , 2 UNION ALL
SELECT 5 , 3 UNION ALL
SELECT 6 , 8 UNION ALL
SELECT 7 , 9
)
,Cte2
AS
(
SELECT Id ,
ParentId ,
CAST('\'+ CAST(Id AS VARCHAR(MAX))AS VARCHAR(MAX)) AS [Hierarchy]
FROM CTE
WHERE ParentId IS NULL
UNION ALL
SELECT c1.Id ,
c1.ParentId ,
[Hierarchy]+'\'+ CAST(c1.Id AS VARCHAR(MAX)) AS [Hierarchy]
FROM Cte2 c2
INNER JOIN CTE c1
ON c1.ParentId = c2.Id
)
SELECT Id,
RIGHT([Hierarchy],LEN([Hierarchy])-1) AS ParentChildHierarchy
FROM Cte2
GO
Result
Id ParentChildHierarchy
-------------------------
1 1
2 1\2
3 1\3
5 1\3\5
4 1\2\4

This query will help you
CREATE TABLE #table( ID INT, ParentId INT )
INSERT INTO #table(ID,ParentId)
VALUES (1, NULL ), (2, 1 ), (3, 1 ), (4, 2 ), (5, 3 ), (6, 8 ), (7, 9 )
;WITH CTE AS (
SELECT ID FROM #table WHERE PARENTID IS NULL
UNION ALL
SELECT T.ID FROM #table T
INNER JOIN #table T1 ON T.PARENTID =T1.ID
) SELECT * FROM CTE

Related

Get rows in all levels with parentId SQL

I have a table Categories, and each row in this tables have a parentId. The parent is just an other row in the same table.
I want to create query to get all children in different levels by just giving the Id of the first Parent.
With recursive cte:
DECLARE #t TABLE ( id INT, pid INT )
INSERT INTO #t
VALUES ( 1, NULL ),
( 2, NULL ),
( 3, 1 ),
( 4, 1 ),
( 5, 3 ),
( 6, 5 ),
( 7, 6 ),
( 8, 6 )
DECLARE #p INT = 1;
WITH cte
AS ( SELECT *
FROM #t
WHERE pid = #p
UNION ALL
SELECT t.*
FROM #t t
JOIN cte c ON c.id = t.pid
)
SELECT *
FROM cte c
Output:
id pid
3 1
4 1
5 3
6 5
7 6
8 6
EDIT:
To use in another select statement:
WITH cte
AS ( SELECT *
FROM #t
WHERE pid = #p
UNION ALL
SELECT t.*
FROM #t t
JOIN cte c ON c.id = t.pid
)
SELECT *
FROM cte c
JOIN AnotherTable t on c.id = t.id

SQL Server Create Grouping For Related Records

I'm running into an interesting scenario trying to assign an arbitrary FamilyId to fields that are related to each other.
Here is the structure that we're currently working with:
DataId OriginalDataId
3 1
4 1
5 1
6 1
3 2
4 2
5 2
6 2
7 10
8 10
9 10
11 15
What we're attempting to do is add a FamilyId column to all DataIds that have a relationship between each other.
In this case, Id's 3, 4, 5, and 6 have a relationship to 1. But 3, 4, 5, and 6 also have a relationship with 2. So 1, 2, 3, 4, 5, and 6 should all be considered to be in the same FamilyId.
7, 8, and 9 only have a relationship to 10, which puts this into a separate FamilyId. Same for 11 and 15.
What I am expecting as a result from this are the following results:
DataId FamilyId
1 1
2 1
3 1
4 1
5 1
6 1
7 2
8 2
9 2
10 2
11 3
15 3
Sample data, structure, and queries:
Declare #Results_Stage Table
(
DataId BigInt Not Null,
OriginalDataId BigInt Null
)
Insert #Results_Stage
Values (3,1), (4,1), (5,1), (6,1), (3,2), (4,2), (5,2), (6,2), (7,10), (8, 10), (9, 10), (11, 15)
Select DataId, Row_Number() Over(Partition By DataId Order By OriginalDataId Asc) FamilyId
From #Results_Stage R
Union
Select OriginalDataId, Row_Number() Over(Partition By DataId Order By OriginalDataId Asc) FamilyId
From #Results_Stage
I'm positive my attempt is nowhere near correct, but I'm honestly not sure where to even start on this -- or if it's even possible in SQL Server.
Does anyone have an idea on how to tackle this issue, or at least, something to point me in the right direction?
Edit Below is a query I've come up with so far to identify the other DataId records that should belong to the same FamilyId
Declare #DataId BigInt = 1
;With Children As
(
Select Distinct X.DataId
From #Results_Stage S
Outer Apply
(
Select Distinct DataId
From #Results_Stage R
Where R.OriginalDataId = S.DataId
Or R.OriginalDataId = S.OriginalDataId
) X
Where S.DataId = #DataId
Or S.OriginalDataId = #DataId
)
Select Distinct O.OriginalDataId
From Children C
Outer Apply
(
Select S.OriginalDataId
From #Results_Stage S
Where S.DataId = C.DataId
) O
Union
Select DataId
From Children
The following query, which employs FOR XML PATH:
SELECT R.OriginalDataId,
STUFF((
SELECT ', ' + + CAST([DataId] AS VARCHAR(MAX))
FROM #Results_Stage
WHERE (OriginalDataId = R.OriginalDataId)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS GroupValues
FROM #Results_Stage R
GROUP BY R.OriginalDataId
can be used to produce this output:
OriginalDataId GroupValues
===========================
1 3, 4, 5, 6
2 3, 4, 5, 6
10 7, 8, 9
15 11
Using the above result set, we can easily identify each group and thus have something upon which DENSE_RANK() can be applied:
;WITH GroupedData AS (
SELECT R.OriginalDataId,
STUFF((
SELECT ', ' + + CAST([DataId] AS VARCHAR(MAX))
FROM #Results_Stage
WHERE (OriginalDataId = R.OriginalDataId)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS GroupValues
FROM #Results_Stage R
GROUP BY R.OriginalDataId
), Families AS (
SELECT OriginalDataId, DENSE_RANK() OVER (ORDER BY GroupValues) AS FamilyId
FROM GroupedData
)
SELECT OriginalDataId AS DataId, FamilyId
FROM Families
UNION
SELECT DataId, F.FamilyId
FROM #Results_Stage R
INNER JOIN Families F ON R.OriginalDataId = F.OriginalDataId
ORDER BY FamilyId
Output from above is:
DataId FamilyId
===================
11 1
15 1
1 2
2 2
3 2
4 2
5 2
6 2
7 3
8 3
9 3
10 3
Check this ... it doesn't look too nice but is doing the job :)
DECLARE #T TABLE (DataId INT, OriginalDataId INT)
INSERT INTO #T(DataId , OriginalDataId)
select 3,1
union all select 4,1
union all select 5,1
union all select 6,1
union all select 3,2
union all select 4,2
union all select 5,2
union all select 6,2
union all select 7,10
union all select 8,10
union all select 9,10
union all select 11,15
SELECT * FROM #T
;WITH f AS (
SELECT DISTINCT OriginalDataId FROM #T
)
, m AS (
SELECT DISTINCT
DataId , OriginalDataId = MIN(OriginalDataId)
FROM #T
GROUP BY DataId
)
, m2 AS (
SELECT DISTINCT
x.DataId , x.OriginalDataId
FROM #T AS x
LEFT OUTER JOIN m ON x.DataId = m.DataId AND x.OriginalDataId = m.OriginalDataId
WHERE m.DataId IS NULL
)
, m3 AS (
SELECT DISTINCT DataId = x.OriginalDataId , m.OriginalDataId
FROM m2 AS x
INNER JOIN m ON x.DataId = m.DataId
)
, m4 AS (
SELECT DISTINCT
DataId = OriginalDataId , OriginalDataId
FROM #T
WHERE OriginalDataId NOT IN(SELECT DataId FROM m3)
UNION
SELECT DISTINCT
x.DataId , f.OriginalDataId
FROM f
INNER JOIN m AS x on x.OriginalDataId = f.OriginalDataId
WHERE x.DataId NOT IN(SELECT DataId FROM m3)
UNION
SELECT DataId , OriginalDataId FROM m3
)
, list AS (
SELECT
x.DataId, FamilyId = DENSE_RANK() OVER(ORDER BY x.OriginalDataId )
FROM m4 AS x
)
SELECT * FROM list
-- OUTPUT
DataId FamilyId
1 1
2 1
3 1
4 1
5 1
6 1
7 2
8 2
9 2
10 2
11 3
15 3

Need a query to insert 'level' into an adjacent list

I have a table like so
ID Node ParentID
1 A 0
2 B 1
3 C 1
4 D 2
5 E 2
6 F 3
7 G 3
8 H 3
9 I 4
10 J 4
11 K 10
12 L 11
I need a query to generate a 'level' field that shows how many levels deep a particular node is. Example below
ID Node ParentID Level
1 A 0 1
2 B 1 2
3 C 1 2
4 D 2 3
5 E 2 3
6 F 3 4
7 G 3 4
8 H 3 4
9 I 4 5
10 J 4 5
11 K 10 6
12 L 11 7
Select Id,
Node,
ParentID,
Dense_Rank() Over(Order by ParentID) as Level
from Table_Name
SQL Fiddle Demo
You can use DENSE_RANK function
SELECT i.ID, p.Node, i.ParentID
,Dense_Rank() Over(Order by ParentID) as Level
FROM TableName AS i;
for more detail visit: http://blog.sqlauthority.com/2007/10/09/sql-server-2005-sample-example-of-ranking-functions-row_number-rank-dense_rank-ntile/
I think the correct way to do it will be to get the parent level and increment it by 1 when inserting the data since all other ways are expensive performance wise.
Something like:
;with tree (ID, ParentID, Level)
as (
select ID, ParentID, 1 from TableName where ParentID = 0
union all
select t.ID, t.ParentID, 1 + tree.Level
from Tree join TableName t on t.ParentID = Tree.ID
)
select ID, Level from Tree
Try this
CREATE TABLE #Table1
([ID] int, [Node] varchar(1), [ParentID] int)
;
INSERT INTO #Table1
([ID], [Node], [ParentID])
VALUES
(1, 'A', 0),
(2, 'B', 1),
(3, 'C', 1),
(4, 'D', 2),
(5, 'E', 2),
(6, 'F', 3),
(7, 'G', 3),
(8, 'H', 3),
(9, 'I', 4),
(10, 'J', 4),
(11, 'K', 10),
(12, 'L', 11)
;
;WITH CTE ([ID], [ParentID], [Node], [Level])
as (
SELECT [ID], [ParentID], [Node], 1 FROM #Table1 WHERE ParentID = 0
UNION all
select t.[ID], t.[ParentID], t.[Node], 1 + c.[Level]
from CTE c inner join #Table1 t ON t.[ParentID] = c.[ID]
)
select ID, [Node], [ParentID], [Level] from CTE
ORDER BY [Node]
DROP TABLE #Table1
Here, you need to set level by grouping ParentID then join both tables by ParentID.
WITH CTE (ParentID, Level)
AS (
SELECT ParentID
, Row_Number() OVER (ORDER BY ParentID) AS Level
FROM Table1
GROUP BY ParentID
)
SELECT t1.ID, t1.Node, t1.ParentID, CTE.Level
FROM Table1 t1
JOIN CTE ON t1.ParentID = CTE.ParentID;
See this SQLFiddle
Update: (for MySQL - just to help others)
To do the same in MySQL try to get row number like this:
SELECT t1.ID, t1.Node, t1.ParentID, Tbl.Level
FROM Table1 t1
JOIN
(
SELECT #Level:=#Level+1 AS Level , ParentID
FROM (SELECT DISTINCT ParentID FROM Table1) t
, (SELECT #Level:=0) r
ORDER BY ParentID
) Tbl
ON t1.ParentID = Tbl.ParentID;
See this SQLFiddle

accessing parent nodes in same level in T-SQL

Let's say I have a database schema like this:
RowId ParentId Name
------ ---------- ------
1 NULL Level1
2 NULL Level2
3 1 Leaf1
4 1 Leaf2
5 2 Leaf1
6 3 LeafX
Basically, the tree would look as such:
Level1
Leaf1
LeafX
Leaf2
Level2
Leaf1
I need to extract all ancestor LEVEL of LeafX in the most efficient and dynamic way.
So it will output: Leaf1, Leaf2, and Leaf1 (of Level2)
How do I do this in T-SQL? Thanks
This will give you the result you want.
;with C as
(
select T.rowid,
T.parentid,
T.name,
1 as Lvl
from YourTable as T
where T.parentid is null
union all
select T.rowid,
T.parentid,
T.name,
C.Lvl + 1
from YourTable as T
inner join C
on T.parentid = C.rowid
)
select *
from C
where C.Lvl = (
select C.lvl-1
from C
where C.name = 'LeafX'
)
Update
And this might be faster for you. You have to test on your data.
declare #Level int;
with C as
(
select T.rowid,
T.parentid
from #t as T
where T.name = 'LeafX'
union all
select T.rowid,
T.parentid
from #t as T
inner join C
on T.rowid = C.parentid
)
select #Level = count(*) - 1
from C;
with C as
(
select T.rowid,
T.parentid,
T.name,
1 as Lvl
from #t as T
where T.parentid is null
union all
select T.rowid,
T.parentid,
T.name,
C.Lvl + 1
from #t as T
inner join C
on T.parentid = C.rowid
where C.Lvl < #Level
)
select *
from C
where C.Lvl = #Level;
There's a few methods to do that. My favourite is to create special table Trees_Parents, where you will store every parent for evere node.
So if have structure like that
RowId ParentId Name
------ ---------- ------
1 NULL Level1
2 NULL Level2
3 1 Leaf1
4 1 Leaf2
5 2 Leaf1
6 3 LeafX
your Trees_Parents table will looks like
RowId ParentId
------ ----------
1 1
2 2
3 3
3 1
4 4
4 1
5 5
5 2
6 6
6 1
6 3
then when you need to retrieve all children you just write
select RowID from Trees_Parents where ParentId = 1
I'm storing row self in this table to avoid unions, if you don't need it you can write
select RowID from Trees_Parents where ParentId = 1 and ParentId <> RowId
And for all parents you'll write
select ParentId from Trees_Parents where RowId = 6 and ParentId <> RowId
You can also store Table_Name in table Trees_Parents so you can use it for different tables
Another way is to write recursive WITH clause, but if your tree is big and it's not changing frequently I think it's better to store parents data in additional table
Well you can use recursive solution. You need to get all nodes with Depth = Depth of your node - 1
declare #Temp table (RowId int, ParentId int, Name nvarchar(128))
insert into #Temp
select 1, null, 'Level1' union all
select 2, null, 'Level2' union all
select 3, 1, 'Leaf1' union all
select 4, 1, 'Leaf2' union all
select 5, 2, 'Leaf3' union all
select 6, 3, 'LeafX';
with Parents
as
(
select T.RowId, 0 as Depth from #Temp as T where T.ParentId is null
union all
select T.RowId, P.Depth + 1
from Parents as P
inner join #Temp as T on T.ParentId = P.RowId
)
select T.Name
from Parents as P
outer apply (select TT.Depth from Parents as TT where TT.RowId = 6) as CALC
left outer join #Temp as T on T.RowId = P.RowId
where P.Depth = CALC.Depth - 1
declare #t table(rowid int, parentid int, name varchar(10))
insert #t values(1,NULL,'Level1')
insert #t values(2,NULL,'Level2')
insert #t values(3,1,'Leaf1')
insert #t values(4,1,'Leaf2')
insert #t values(5,2,'Leaf1')
insert #t values(6,3,'LeafX')
;with a as
(
select rowid, parentid, 0 level from #t where name = 'leafx'
union all
select t.rowid, t.parentid, level + 1 from #t t
join a on a.parentid = t.rowid
), b as
(
select rowid, parentid,name, 0 level from #t where parentid is null
union all
select t.rowid, t.parentid,t.name, level + 1
from b join #t t on b.rowid = t.parentid
)
select rowid, parentid, name from b
where level = (select max(level)-1 from a)
rowid parentid name
5 2 Leaf1
3 1 Leaf1
4 1 Leaf2

How to get the full Hierarchy with SQL CTE

Hi I am trying to get the full hierarchy of my category. Here is my sample table
ID PARENT_ID NAME DEPTH
------------------------------------------
1 NULL A 1
2 NULL B 1
3 NULL C 1
4 1 D 2
5 4 E 3
The cte output should be this
ID PARENT_ID NAME
---------------------------
1 NULL A
2 NULL B
3 NULL C
4 1 D
5 4 E
5 1 E
As you can see id:5 is parent of 4 and 1. How do i query the whole tree
DECLARE #tmp TABLE(ID INT,ParentID INT,NAME VARCHAR(10),DEPTH INT)
INSERT INTO #tmp VALUES
(1 ,NULL ,'A' ,1 ),
(2 ,NULL ,'B' ,1 ),
(3 ,NULL ,'C' ,1 ),
(4 ,1 ,'D' ,2 ),
(5 ,4 ,'E' ,3 ),
(6 ,5 ,'F' ,4 );
select * from #tmp
;WITH cte AS
(
SELECT A.ID
,A.ParentID
,A.NAME
,A.DEPTH
FROM #tmp A
UNION ALL
SELECT A.ID
,B.ParentID
,A.NAME
,A.DEPTH
FROM cte A
INNER JOIN #tmp B on A.ParentID = B.ID
WHERE B.ParentID IS NOT NULL
)
SELECT * FROM cte
In SQL Server 2008 CTE can be use to query recursively.
Example of CTE from MSDN
-- Sample Solution (Untested)
;With TableCTE(Id, Name, ParentId, Depth)
(
Select ID,Name,ParentId, 1
FROM MyTable
Union All
Select C.Id, C.Name, t.ParentId, c.Depth +1
FROM #tmp t
INNER JOIN TableCTE C on t.Id = c.ParentId
-- Where t.ParentId IS Not Null
)
SELECT Id, Name, ParentId
FROM TableCTE
;WITH cte AS
(
SELECT A.ID
,A.ParentID
,A.NAME
,A.DEPTH
FROM #tmp A
WHERE A.ParentID IS NULL
UNION ALL
SELECT B.ID
,B.ParentID
,B.NAME
,B.DEPTH
FROM cte A
INNER JOIN
#tmp B on B.ParentID = A.ID
WHERE B.ParentID IS NOT NULL
)
SELECT * FROM cte