Display Upper Most Hierarchy Parent for Child - sql

I have a table with parent child relationships and I want to, for each child, display the upper most parent. The order of the real table won't be ordered so consecutively so any kind of sorting isn't desirable. I also want to avoid doing manual joins for the max number of hierarchy levels.
Table:
Parent Child
1 2
2 3
3 4
5 6
6 7
So for Child 4 has Parent 3, who has Parent 2, who has Parent 1.
4, 3, 2 should all have Parent 4 in the output.
Desired Output:
Parent Child
1 2
1 3
1 4
5 6
5 7

;with SelectedToTopCTE as
( select ParentID, ChildID as Child, 1 as level
from Table
union all
select d.ParentID, s.ChildID, d.level + 1
from SelectedToTopCTE as d
join Table s
on d.Child = s.ParentID
)
select * INTO #SelectedToTop
from SelectedToTopCTE;
SELECT Child, MAX(level) as MaxLevel INTO #UpperMostSPLT
FROM #SelectedToTop
group by Child;
SELECT A.*
FROM #SelectedToTop A
INNER JOIN
#UpperMostSPLT B
ON A.Child = B.Child AND A.level = B.MaxLevel
ORDER BY ParentID;

A recursive Common Table Expression (CTE) makes quick work of walking through parent/child hierarchies. This example starts with the top level parents, i.e. the rows that have no parents above them. It then adds children one level at a time while keeping track of the topmost parent.
-- Sample data.
declare #Samples as Table ( Parent Int, Child Int );
insert into #Samples ( Parent, Child ) values
( 1, 2 ), ( 2, 3 ), ( 3, 4 ), ( 5, 6 ), ( 6, 7 );
select * from #Samples;
-- Run the tree.
with Tree as (
-- Start with the top level parents.
select Parent as TopLevelParent, Child
from #Samples as S
-- Where the row has no parent above it.
where not exists ( select 42 from #Samples as SS where S.Parent = SS.Child )
union all
-- Add the children one level at a time.
select T.TopLevelParent, S.Child
from Tree as T inner join
#Samples as S on S.Parent = T.Child )
-- Display the sorted results.
select TopLevelParent, Child
from Tree
order by TopLevelParent, Child;

Related

LAG All Previous Rows - Parent Child Database Find All Total Children Count From Each Parent

I'm trying to obtain all children count from each parent (where subchildren counts).
This is the sample database (MSSQL).
INSERT INTO NODES VALUES(NULL, 1);
INSERT INTO NODES VALUES(NULL, 2);
INSERT INTO NODES VALUES(2, 3);
INSERT INTO NODES VALUES(1, 4);
INSERT INTO NODES VALUES(1, 5);
INSERT INTO NODES VALUES(3, 6);
INSERT INTO NODES VALUES(NULL, 7);
INSERT INTO NODES VALUES(NULL, 8);
INSERT INTO NODES VALUES(8, 9);
INSERT INTO NODES VALUES(7, 10);
INSERT INTO NODES VALUES(9, 11);
INSERT INTO NODES VALUES(11, 12);
INSERT INTO NODES VALUES(10, 13);
INSERT INTO NODES VALUES(10, 14);
INSERT INTO NODES VALUES(4, 15);
Where the hierarchy is;
- 1
- 4
- 15
- 5
- 2
- 3
- 6
- 7
- 10
- 13
- 14
- 8
- 9
- 11
- 12
And the desired result is:
id
Children Count
1
3
4
1
5
0
2
2
3
1
6
0
7
3
10
2
13
1
14
0
8
3
9
2
11
1
12
0
Every time I make a strategy to formulate a query, I get to the point where I must iterate the table formed by the query at running time.
If I go deep out grouping the results (with the parentid) I could generate a count of only the children (not the subchildren), so that we can add up to the root. But clearly I would need to iterate the table that I have been forming in the query, I don't know if it would be correct to say recursively.
To express myself better I will show it with the part I have reached and what I want to do;
WITH tree AS
(
SELECT n1.parentid, n1.id, 1 AS level
FROM NODES AS n1
WHERE n1.parentid IS NULL
UNION ALL
SELECT n2.parentid, n2.id, level + 1 AS level
FROM NODES AS n2
INNER JOIN tree ON n2.parentid = tree.id
), levels AS
(
SELECT *
FROM tree
)
SELECT parentid, id, (COUNT(*) OVER(PARTITION BY parentid ORDER BY parentid)) AS childrencountofparentid,
ROW_NUMBER() OVER(ORDER BY parentid DESC) AS rownumber
FROM levels
Where the output is:
parentid
id
childrencountofparentid
rownumber
11
12
1
1
10
13
2
2
10
14
2
3
9
11
1
4
8
9
1
5
7
10
1
6
4
15
1
7
3
6
1
8
2
3
1
9
1
4
2
10
1
5
2
11
null
1
4
12
null
2
4
13
null
7
4
14
null
8
4
15
I want to do this:
Full Image
I want to use the results of the previous rows, similar to lag but i must iterate all previous rows.
I'll solve this with the hierarchyid datatype. First, a recursive CTE to calculate that value for each row in your dataset.
with cte as (
select ID, ParentID,
h = cast(concat('/', ID, '/') as varchar(100))
from NODES
where ParentID is null
union all
select child.ID, child.ParentID,
h = cast(concat(Parent.h, child.ID, '/') as varchar(100))
from NODES as child
join cte as Parent
on child.ParentID = Parent.ID
)
select ID, ParentID,
h = cast(h as hierarchyid)
into #t
from cte;
The only "tricky" thing here is I'm building a string as I'm traversing the hierarchy that can be converted to the hierarchyid datatype.
From there, it's fairly easy to count children with what amounts to a self-join.
select ID, h.ToString(), count(child.a)
from #t as parent
outer apply (
select a = 1
from #t as child
where child.h.IsDescendantOf(parent.h) = 1
and child.ID <> Parent.ID
) as child
group by parent.ID, h
order by h;
Note - I'm only including the h column in the result set to a) show the path back to parent and b) to provide ordering. If you need neither of those, it's not necessary to include. One note - in your desired results, you have 13 as having a child count of 1. I don't see anywhere in the data (or in your helpfully provided visualization of the hierarchy) that 13 has any children. I do see that it has a sibling in ID 14 - if that needs to be counted, the approach needs to change slightly.
One other thing to note here - if you can maintain the hierarchyid column in the source data (which isn't hard to do, by the way), there's no need for a recursive CTE at all. I like a mix of the two approaches. Which is to say keep the notion of ParentID as a column but use the hierarchyid in calculations. If the hierarchyid gets "corrupted" (which is possible given that it's really a cached/computed value that's derived from base data and application logic), it can be recalculated from the ID/ParentID data.
Here is solution that works if you don't care for id order. Unfortunately, I don't know how to make it ordered as you did in desired output. If nothing I hope it helps you come to solution as data is good.
WITH cte as (
SELECT p.Id as 'PID', c.Id as 'CID' FROM nodes p left join nodes c on p.id = c.ParentId
UNION ALL
SELECT c.PID,p.ID FROM cte c JOIN nodes p ON c.CID=p.ParentId
)
SELECT PID as id, count(*) as 'Children Count' FROM cte where CID IS NOT NULL GROUP BY PID
UNION ALL
SELECT PID, 0 FROM cte WHERE CID IS NULL GROUP BY PID
ORDER BY PID ASC

To find infinite recursive loop in CTE

I'm not a SQL expert, but if anybody can help me.
I use a recursive CTE to get the values as below.
Child1 --> Parent 1
Parent1 --> Parent 2
Parent2 --> NULL
If data population has gone wrong, then I'll have something like below, because of which CTE may go to infinite recursive loop and gives max recursive error. Since the data is huge, I cannot check this bad data manually. Please let me know if there is a way to find it out.
Child1 --> Parent 1
Parent1 --> Child1
or
Child1 --> Parent 1
Parent1 --> Parent2
Parent2 --> Child1
With Postgres it's quite easy to prevent this by collecting all visited nodes in an array.
Setup:
create table hierarchy (id integer, parent_id integer);
insert into hierarchy
values
(1, null), -- root element
(2, 1), -- first child
(3, 1), -- second child
(4, 3),
(5, 4),
(3, 5); -- endless loop
Recursive query:
with recursive tree as (
select id,
parent_id,
array[id] as all_parents
from hierarchy
where parent_id is null
union all
select c.id,
c.parent_id,
p.all_parents||c.id
from hierarchy c
join tree p
on c.parent_id = p.id
and c.id <> ALL (p.all_parents) -- this is the trick to exclude the endless loops
)
select *
from tree;
To do this for multiple trees at the same time, you need to carry over the ID of the root node to the children:
with recursive tree as (
select id,
parent_id,
array[id] as all_parents,
id as root_id
from hierarchy
where parent_id is null
union all
select c.id,
c.parent_id,
p.all_parents||c.id,
p.root_id
from hierarchy c
join tree p
on c.parent_id = p.id
and c.id <> ALL (p.all_parents) -- this is the trick to exclude the endless loops
and c.root_id = p.root_id
)
select *
from tree;
Update for Postgres 14
Postgres 14 introduced the (standard compliant) CYCLE option to detect cycles:
with recursive tree as (
select id,
parent_id
from hierarchy
where parent_id is null
union all
select c.id,
c.parent_id
from hierarchy c
join tree p
on c.parent_id = p.id
)
cycle id -- track cycles for this column
set is_cycle -- adds a boolean column is_cycle
using path -- adds a column that contains all parents for the id
select *
from tree
where not is_cycle
You haven't specified the dialect or your column names, so it is difficult to make the perfect example...
-- Some random data
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL
DROP TABLE #MyTable
CREATE TABLE #MyTable (ID INT PRIMARY KEY, ParentID INT NULL, Description VARCHAR(100))
INSERT INTO #MyTable (ID, ParentID, Description) VALUES
(1, NULL, 'Parent'), -- Try changing the second value (NULL) to 1 or 2 or 3
(2, 1, 'Child'), -- Try changing the second value (1) to 2
(3, 2, 'SubChild')
-- End random data
;WITH RecursiveCTE (StartingID, Level, Parents, Loop, ID, ParentID, Description) AS
(
SELECT ID, 1, '|' + CAST(ID AS VARCHAR(MAX)) + '|', 0, * FROM #MyTable
UNION ALL
SELECT R.StartingID, R.Level + 1,
R.Parents + CAST(MT.ID AS VARCHAR(MAX)) + '|',
CASE WHEN R.Parents LIKE '%|' + CAST(MT.ID AS VARCHAR(MAX)) + '|%' THEN 1 ELSE 0 END,
MT.*
FROM #MyTable MT
INNER JOIN RecursiveCTE R ON R.ParentID = MT.ID AND R.Loop = 0
)
SELECT StartingID, Level, Parents, MAX(Loop) OVER (PARTITION BY StartingID) Loop, ID, ParentID, Description
FROM RecursiveCTE
ORDER BY StartingID, Level
Something like this will show if/where there are loops in the recursive cte. Look at the column Loop. With the data as is, there is no loops. In the comments there are examples on how to change the values to cause a loop.
In the end the recursive cte creates a VARCHAR(MAX) of ids in the form |id1|id2|id3| (called Parents) and then checks if the current ID is already in that "list". If yes, it sets the Loop column to 1. This column is checked in the recursive join (the ABD R.Loop = 0).
The ending query uses a MAX() OVER (PARTITION BY ...) to set to 1 the Loop column for a whole "block" of chains.
A little more complex, that generates a "better" report:
-- Some random data
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL
DROP TABLE #MyTable
CREATE TABLE #MyTable (ID INT PRIMARY KEY, ParentID INT NULL, Description VARCHAR(100))
INSERT INTO #MyTable (ID, ParentID, Description) VALUES
(1, NULL, 'Parent'), -- Try changing the second value (NULL) to 1 or 2 or 3
(2, 1, 'Child'), -- Try changing the second value (1) to 2
(3, 3, 'SubChild')
-- End random data
-- The "terminal" childrens (that are elements that don't have childrens
-- connected to them)
;WITH WithoutChildren AS
(
SELECT MT1.* FROM #MyTable MT1
WHERE NOT EXISTS (SELECT 1 FROM #MyTable MT2 WHERE MT1.ID != MT2.ID AND MT1.ID = MT2.ParentID)
)
, RecursiveCTE (StartingID, Level, Parents, Descriptions, Loop, ParentID) AS
(
SELECT ID, -- StartingID
1, -- Level
'|' + CAST(ID AS VARCHAR(MAX)) + '|',
'|' + CAST(Description AS VARCHAR(MAX)) + '|',
0, -- Loop
ParentID
FROM WithoutChildren
UNION ALL
SELECT R.StartingID, -- StartingID
R.Level + 1, -- Level
R.Parents + CAST(MT.ID AS VARCHAR(MAX)) + '|',
R.Descriptions + CAST(MT.Description AS VARCHAR(MAX)) + '|',
CASE WHEN R.Parents LIKE '%|' + CAST(MT.ID AS VARCHAR(MAX)) + '|%' THEN 1 ELSE 0 END,
MT.ParentID
FROM #MyTable MT
INNER JOIN RecursiveCTE R ON R.ParentID = MT.ID AND R.Loop = 0
)
SELECT * FROM RecursiveCTE
WHERE ParentID IS NULL OR Loop = 1
This query should return all the "last child" rows, with the full parent chain. The column Loop is 0 if there is no loop, 1 if there is a loop.
Here's an alternate method for detecting cycles in adjacency lists (parent/child relationships) where nodes can only have one parent which can be enforced with a unique constraint on the child column (id in the table below). This works by computing the closure table for the adjacency list via a recursive query. It starts by adding every node to the closure table as its own ancestor at level 0 then iteratively walks the adjacency list to expand the closure table. Cycles are detected when a new record's child and ancestor are the same at any level other than the original level zero (0):
-- For PostgreSQL and MySQL 8 use the Recursive key word in the CTE code:
-- with RECURSIVE cte(ancestor, child, lev, cycle) as (
with cte(ancestor, child, lev, cycle) as (
select id, id, 0, 0 from Table1
union all
select cte.ancestor
, Table1.id
, case when cte.ancestor = Table1.id then 0 else cte.lev + 1 end
, case when cte.ancestor = Table1.id then cte.lev + 1 else 0 end
from Table1
join cte
on cte.child = Table1.PARENT_ID
where cte.cycle = 0
) -- In oracle uncomment the next line
-- cycle child set isCycle to 'Y' default 'N'
select distinct
ancestor
, child
, lev
, max(cycle) over (partition by ancestor) cycle
from cte
Given the following adjacency list for Table1:
| parent_id | id |
|-----------|----|
| (null) | 1 |
| (null) | 2 |
| 1 | 3 |
| 3 | 4 |
| 1 | 5 |
| 2 | 6 |
| 6 | 7 |
| 7 | 8 |
| 9 | 10 |
| 10 | 11 |
| 11 | 9 |
The above query which works on SQL Sever (and Oracle, PostgreSQL and MySQL 8 when modified as directed) rightly detects that nodes 9, 10, and 11 participate in a cycle of length 3.
SQL(/DB) Fiddles demonstrating this in various DBs can be found below:
Oracle 11gR2
SQL Server 2017
PostgeSQL 9.5
MySQL 8
You can use the same approach described by Knuth for detecting a cycle in a linked list here. In one column, keep track of the children, the children's children, the children's children's children, etc. In another column, keep track of the grandchildren, the grandchildren's grandchildren, the grandchildren's grandchildren's grandchildren, etc.
For the initial selection, the distance between Child and Grandchild columns is 1. Every selection from union all increases the depth of Child by 1, and that of Grandchild by 2. The distance between them increases by 1.
If you have any loop, since the distance only increases by 1 each time, at some point after Child is in the loop, the distance will be a multiple of the cycle length. When that happens, the Child and the Grandchild columns are the same. Use that as an additional condition to stop the recursion, and detect it in the rest of your code as an error.
SQL Server sample:
declare #LinkTable table (Parent int, Child int);
insert into #LinkTable values (1, 2), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7), (7, 1);
with cte as (
select lt1.Parent, lt1.Child, lt2.Child as Grandchild
from #LinkTable lt1
inner join #LinkTable lt2 on lt2.Parent = lt1.Child
union all
select cte.Parent, lt1.Child, lt3.Child as Grandchild
from cte
inner join #LinkTable lt1 on lt1.Parent = cte.Child
inner join #LinkTable lt2 on lt2.Parent = cte.Grandchild
inner join #LinkTable lt3 on lt3.Parent = lt2.Child
where cte.Child <> cte.Grandchild
)
select Parent, Child
from cte
where Child = Grandchild;
Remove one of the LinkTable records that causes the cycle, and you will find that the select no longer returns any data.
Try to limit the recursive result
WITH EMP_CTE AS
(
SELECT
0 AS [LEVEL],
ManagerId, EmployeeId, Name
FROM Employees
WHERE ManagerId IS NULL
UNION ALL
SELECT
[LEVEL] + 1 AS [LEVEL],
ManagerId, EmployeeId, Name
FROM Employees e
INNER JOIN EMP_CTE c ON e.ManagerId = c.EmployeeId
AND s.LEVEL < 100 --RECURSION LIMIT
)
SELECT * FROM EMP_CTE WHERE [Level] = 100
Here is the solution for SQL Server:
Table Insert script:
CREATE TABLE MyTable
(
[ID] INT,
[ParentID] INT,
[Name] NVARCHAR(255)
);
INSERT INTO MyTable
(
[ID],
[ParentID],
[Name]
)
VALUES
(1, NULL, 'A root'),
(2, NULL, 'Another root'),
(3, 1, 'Child of 1'),
(4, 3, 'Grandchild of 1'),
(5, 4, 'Great grandchild of 1'),
(6, 1, 'Child of 1'),
(7, 8, 'Child of 8'),
(8, 7, 'Child of 7'), -- This will cause infinite recursion
(9, 1, 'Child of 1');
Script to find the exact records which are the culprit:
;WITH RecursiveCTE
AS (
-- Get all parents:
-- Any record in MyTable table could be an Parent
-- We don't know here yet which record can involve in an infinite recursion.
SELECT ParentID AS StartID,
ID,
CAST(Name AS NVARCHAR(255)) AS [ParentChildRelationPath]
FROM MyTable
UNION ALL
-- Recursively try finding all the childrens of above parents
-- Keep on finding it until this child become parent of above parent.
-- This will bring us back in the circle to parent record which is being
-- keep in the StartID column in recursion
SELECT RecursiveCTE.StartID,
t.ID,
CAST(RecursiveCTE.[ParentChildRelationPath] + ' -> ' + t.Name AS NVARCHAR(255)) AS [ParentChildRelationPath]
FROM RecursiveCTE
INNER JOIN MyTable AS t
ON t.ParentID = RecursiveCTE.ID
WHERE RecursiveCTE.StartID != RecursiveCTE.ID)
-- FInd the ones which causes the infinite recursion
SELECT StartID,
[ParentChildRelationPath],
RecursiveCTE.ID
FROM RecursiveCTE
WHERE StartID = ID
OPTION (MAXRECURSION 0);
Output of above query:

Algorithm behind Nested comments with SQL

I want to know the algorithm behind child nested records are displayed within parent nested records for example
Comment 1 (parent record)
reply 1 (child record)
reply 2 (child record)
reply 3 (child record)
view all
Comment 2 (parent record)
reply 1 (child record)
reply 2 (child record)
reply 3 (child record)
view all
how is a query written to get the above results?
You can use a Recursive Common Table Expression like the one below
;WITH comments AS
(
SELECT 1 as ID,'Comment 1' detail,NULL AS ParentID
UNION ALL SELECT 2 as ID,'Comment 2',NULL AS ParentID
UNION ALL SELECT 3 as ID,'Reply 1',1 AS ParentID
UNION ALL SELECT 4 as ID,'Reply 2',3 AS ParentID
UNION ALL SELECT 5 as ID,'Reply 3',4 AS ParentID
UNION ALL SELECT 6 as ID,'Reply 4',2 AS ParentID
UNION ALL SELECT 7 as ID,'Reply 5',6 AS ParentID
),comment_hierarchy AS
(
SELECT ID,detail,ID AS prid,0 AS orderid
FROM comments
WHERE ParentID IS NULL
UNION ALL
SELECT c.ID,c.detail ,ch.prid as prid,ch.orderid + 1
FROM comments c
INNER JOIN comment_hierarchy ch
ON c.ParentID = ch.ID
)
SELECT ID,Detail
FROM comment_hierarchy
ORDER BY prid,orderid asc
For more info refer
https://technet.microsoft.com/en-us/library/ms186243%28v=sql.105%29.aspx
CTE to get all children (descendants) of a parent

SQL to retrieve parent-child relationship in parent-child order, from a self referencing table

I use the following query to retrieve the parent-child relationship data, from a table which is self referencing to the parent.
-- go down the hierarchy and get the childs
WITH ChildLocations(LocationId, FkParentLocationId, [Level])
AS
(
(
-- Start CTE off by selecting the home location of the user
SELECT l.LocationId, l.FkParentLocationId, 0 as [Level]
FROM Location l
WHERE l.LocationId = #locationId
)
UNION ALL
-- Recursively add locations that are children of records already found in previous iterations.
SELECT l2.LocationId, l2.FkParentLocationId, [Level] + 1
FROM ChildLocations tmp
INNER JOIN Location l2
ON l2.FkParentLocationId = tmp.LocationId
)
INSERT INTO #tmp
SELECT * from ChildLocations;
The table has the following fields:
LocationId, FkParentLocationId, FkLocationTypeId, etc...
This works fine, but how I want to retrieve it is as follows:
Parent 1
Child 1
Child 2
Child 21
Child 3
Child 31
Parent 2
Child 4
Child 5
Child 6
What is currently gives is like:
Parent 1
Parent 2
Child 1
Child 2
Child 3
Child 4
etc....
How can I modify the above to get it in the order I want.
What about to append an 'order' field? This may be an approach:
WITH ChildLocations(LocationId, FkParentLocationId, [Level])
AS
(
(
-- Start CTE off by selecting the home location of the user
SELECT l.LocationId, l.FkParentLocationId, 0 as [Level],
cast( str( l.locationId ) as varchar(max) ) as orderField
FROM Location l
WHERE l.LocationId = #locationId
)
UNION ALL
-- Recursively add locations that are children ...
SELECT l2.LocationId, l2.FkParentLocationId, [Level] + 1,
tmp.orderField + '-' +
str(tmp.locationId) as orderField
FROM ChildLocations tmp
INNER JOIN Location l2
ON l2.FkParentLocationId = tmp.LocationId
)
SELECT * from ChildLocations order by orderField;
Remember than Order by in an Insert is not allowed.
Take a look a sample

Delete all level child item using sql query

I have a table where I have menus listed where I can insert and delete.
Structure goes like:-
ID Name ParentId
1 1. Home 0
2 2. Products 0
3 a. SubProduct1 2
4 b. SubProduct2 2
5 i. Subsub 4
6 ii. ...... 4
7 3. About 0
Top-level menu ParentId is always 0 as displayed in 1, 2 and 7.
Child level items would have ParentId of their parent for ex. Subproduct has 2 as its parentId.
When I delete menu item that time all level child item should be delete irrespective of there levels using SQL query.
There can be any number of levels
The levels can go upto subsubsubsub...... any number.
How about this query:
DECLARE #DelID INT
SET #DelID=1
;WITH T(xParent, xChild)AS
(
SELECT ParentID, ChildId FROM Table WHERE ParentID=#DelID
UNION ALL
SELECT ParentID, ChildId FROM TABLE INNER JOIN T ON ParentID=xChild
)
DELETE FROM TABLE WHERE ParentID IN (SELECT xParent FROM T)
You can use a common table expression to get all the heirarchy items from the item you want to delete to the end of the tree hten
;WITH ParentChildsTree
AS
(
SELECT ID, Name, ParentId
FROM MenuItems
WHERE Id = #itemToDelete
UNION ALL
SELECT ID, Name, ParentId
FROM ParentChildsTree c
INNER JOIN MenuItems t ON c.ParentId = t.Id
)
DELETE FROM MenuItems
WHERE ID IN (SELECT ID FROM ParentChildsTree);
Here is a Demo.
For example if you pass a parameter #itemToDelete = 4 to the query the the items with ids 2 and 4 will be deleted.