SQL - Multi level self join - sql

i have a table which has parent child relationship like this - Isfinal column suggests that it is the final element of that level
ID name ParentId Isfinal
1 abc 0 No
2 acd 1 No
3 ads 1 No
4 xyz 2 No
5 xxy 2 Yes
6 plm 3 No
7 ytr 4 Yes
8 lks 6 Yes
I am trying to write a dynamic query which will give the child element of that ID.
E.G.
If I select 2 then it should give the result as -
ID name ParentId Isfinal
4 xyz 2 No
5 xxy 2 Yes
7 ytr 4 Yes
Is it possible with self join?

Using a recursive CTE , you can solve this.
DECLARE #TABLE TABLE
( ID int
,name nvarchar(200)
,ParentId int
,Isfinal nvarchar(20)
)
INSERT INTO #TABLE
VALUES (1,'abc',0,'No'),(2,'acd',1,'No'),(3,'ads',1,'No'),
(4,'xyz',2,'No'),(5,'xxy',2,'Yes'),(6,'plm',3,'No'),
(7,'ytr',4,'Yes'),(8,'lks',6,'Yes')
DECLARE #ID INT = 2
;WITH CTE
AS
(
SELECT ID,name,ParentId,Isfinal
FROM #TABLE
WHERE ParentId = #ID
UNION ALL
SELECT T.ID,T.name,T.ParentId,T.Isfinal
FROM #TABLE T
INNER JOIN CTE C ON C.ID = T.ParentId
)
SELECT * FROM CTE

Related

Join within same table to retrieve hierarchy (Parent-Child Hierarchy)

I have two tables - Master and Child. Child table contains hierarchal data for MainId stored in Master table.
Master.Name is Unique
Master.MainId = Child.ChildId
Child.ParentId = Child.ChildId
I have to retrieve all data from Child table for Master.RelDate and Master.Name. I thought it was a straight forward query but somehow not able to pull desired output. I'm ending up with just one Child table record no matter how I write query.
Is it to do with table structure or my query?
Note: None of the ParentIds and ChildIds are in sequence. That data comes from another system, I just put those values for simplicity.
IF OBJECT_ID('tempdb..#Master') IS NOT NULL BEGIN DROP TABLE #Master END
GO
IF OBJECT_ID('tempdb..#Child') IS NOT NULL BEGIN DROP TABLE #Child END
GO
CREATE TABLE #Master
(
Id BIGINT IDENTITY(1,1),
RelDate DATE NOT NULL,
MainId BIGINT NOT NULL,
Name VARCHAR(512) NOT NULL
)
GO
CREATE TABLE #Child
(
Id BIGINT IDENTITY(1,1),
RelDate DATE NOT NULL,
ChildId BIGINT NOT NULL,
ParentId BIGINT NULL,
Label VARCHAR(20) NULL,
Value VARCHAR(1024) NULL
)
GO
INSERT INTO #Master(RelDate,MainId,Name) VALUES ('2019-01-01',1,'Name1')
INSERT INTO #Master(RelDate,MainId,Name) VALUES ('2019-01-01',11,'Name11')
GO
INSERT INTO #Child(RelDate,ChildId,ParentId,Label,Value) VALUES
('2019-01-01',1,2,'Level10',NULL)
,('2019-01-01',2,3,'Level09',NULL)
,('2019-01-01',3,4,'Level08',NULL)
,('2019-01-01',4,5,'Level07',NULL)
,('2019-01-01',5,6,'Level06','This is level 6')
,('2019-01-01',6,7,'Level05',NULL)
,('2019-01-01',7,8,'Level04',NULL)
,('2019-01-01',8,9,'Level03','This is level 3')
,('2019-01-01',9,10,'Level02',NULL)
,('2019-01-01',10,11,'Level01',NULL) -- Always same
,('2019-01-01',11,NULL,'Root',NULL) -- Always same
INSERT INTO #Child(RelDate,ChildId,ParentId,Label,Value) VALUES
('2019-01-01',11,12,'Level10',NULL)
,('2019-01-01',12,13,'Level09',NULL)
,('2019-01-01',13,14,'Level08',NULL)
,('2019-01-01',14,15,'Level07',NULL)
,('2019-01-01',15,16,'Level06','This is level 6')
,('2019-01-01',16,17,'Level05',NULL)
,('2019-01-01',17,18,'Level04',NULL)
,('2019-01-01',18,19,'Level03','This is level 3')
,('2019-01-01',19,10,'Level02',NULL)
,('2019-01-01',10,11,'Level01',NULL) -- Always same
,('2019-01-01',11,NULL,'Root',NULL) -- Always same
GO
SELECT * FROM #Master
SELECT * FROM #Child
Desired output for RelDate = 2019-01-01 and Name = Name1
SELECT chld.*
FROM #Master mst (NOLOCK)
INNER JOIN #Child chld (NOLOCK) ON (mst.MainId = chld.ChildId)
LEFT JOIN #Child chld1 (NOLOCK) ON (chld.ParentId = chld.ChildId)
WHERE mst.RelDate = '2019-01-01' -- Input 1
AND mst.Name = 'Name1' -- Input 2
All the data from Child table
Id RelDate ChildId ParentId Label Value
1 2019-01-01 1 2 Level10 NULL
2 2019-01-01 2 3 Level09 NULL
3 2019-01-01 3 4 Level08 NULL
4 2019-01-01 4 5 Level07 NULL
5 2019-01-01 5 6 Level06 This is level 6
6 2019-01-01 6 7 Level05 NULL
7 2019-01-01 7 8 Level04 NULL
8 2019-01-01 8 9 Level03 This is level 3
9 2019-01-01 9 10 Level02 NULL
10 2019-01-01 10 11 Level01 NULL
11 2019-01-01 11 NULL Root NULL
Thanks in advance
select c.* from #Child c inner join #Master M
on M.MainId = C.ChildId
Id RelDate ChildId ParentId Label Value
1 2019-01-01 1 2 Level10 NULL
11 2019-01-01 11 NULL Root NULL
If this isn't the desired output, please provide your desired output.
It seams that data in the tables is kinda non logical. Especially with the 'Name11'. I assume that with every new rows you will lose one id from Childid column .Now we lose 'lvl2' at row 19.
I tried to figure out some solution, now my position here.
It works for 'Name1', but result for 'Name11' a bit different.
SELECT t2.*
FROM #Master mst
join #Child t1 ON mst.MainId = t1.ChildId
join #Child t2 ON t2.parentid >= t1.ChildId
WHERE mst.RelDate = '2019-01-01' -- Input 1
AND mst.Name = 'Name1' -- Input 2
order by t2.id
offset 0 rows
fetch first 10 rows only
Maybe it helps you a little. I will try to find a tricky solution.

Listing all parents hierarchy in delimited string excluding the top most node

Im having a trouble with an already answered question in stackoverflow itself (Question before). So Im just repeating the question with some changes and a trouble in it because of a root element
I have an SQL table like this
ID Name ParentID
------------------------------
0 Users NULL
1 Alex 0
2 John 0
3 Don 1
4 Philip 2
5 Shiva 2
6 San 3
7 Antony 6
8 Mathew 2
9 Cyril 8
10 Johan 9
-------------------------
Am looking for an out put like this
if I pass the ID 7,10,1
The out put table will be
ID Name Relation
------------------------------------
7 Antony Alex->Don->San->Antony
10 Johan John->Mathew->Cyril->Johan
1 Alex -
From the above answer what I was trying to emphasis is it should not consider the top most node Users whose ID is 0 and parentid is null. So for ID 1, it returned just an empty string for relation or just hyphen (-)
How can I achieve that using CTE
Based on prev answer:
DECLARE #t table (ID int not null, Name varchar(19) not null, ParentID int null)
insert into #t(ID,Name,ParentID) values
(1 ,'Alex',null),
(2 ,'John',null),
(3 ,'Don',1),
(4 ,'Philip',2),
(5 ,'Shiva',2),
(6 ,'San',3),
(7 ,'Antony',6),
(8 ,'Mathew',2),
(9 ,'Cyril',8),
(10,'Johan',9)
declare #search table (ID int not null)
insert into #search (ID) values (7),(10), (1);
;With Paths as (
select s.ID as RootID,t.ID,t.ParentID,t.Name
, CASE WHEN t.ParentId IS NULL THEN '-'
ELSE CONVERT(varchar(max),t.Name) END as Path
from #search s
join #t t
on s.ID = t.ID
union all
select p.RootID,t.ID,t.ParentID,p.Name, t.Name + '->' + p.Path
from Paths p
join #t t
on p.ParentID = t.ID
)
select *
from Paths
where ParentID is null;
Rextester Demo
Based on your schema and data, this query:
with P as
(
select *, cast(null as nvarchar) as chain from People where parentID is null
union all
select C.*, cast(coalesce(P.chain, P.name) + '->' + C.name as nvarchar)
from People C inner join P on C.parentID = P.id
)
select id, name, coalesce(chain, '-') relation from P where id in (7, 10, 1)
Yields:
id name relation
----- ------ ------------------------------
1 Alex -
10 Johan John->Mathew->Cyril->Johan
7 Antony Alex->Don->San->Antony
Rextester Demo.

T-SQL recursive query - how to do it?

I have a table with self referencing relation,
ID parentID UserId Title
1 null 100 A
2 1 100 B
3 2 100 C
4 2 100 D
5 null 100 E
6 5 100 F
I want to update UserId from 100 to 101 for all records with ID=1 and its children, so I want to have
ID parentID UserId Title
1 null 101 A
2 1 101 B
3 2 101 C
4 2 101 D
5 null 100 E
6 5 100 F
How can I do it in T-SQL?
You probably want to use a common table expression which allows you to generate recursive queries.
eg:
;with cte as
(
select * from yourtable where id=1
union all
select t.* from cte
inner join yourtable t on cte.id = t.parentid
)
update yourtable
set userid = 101
where id in (select id from cte)

self referencing table with child table

I have a self-referencing table with content like this:
Self-referencing parent table
ID ParentID Name
---------------------
1 John
2 1 Mike
3 2 Erin
4 1 Janie
5 Eric
6 5 Peter
The tree hierarchy should look like this
John
Mike
Erin
Janie
Eric
Peter
And a child table that stores the leaf of parent table that looks like this:
ID Sales
3 100
3 100
4 200
4 200
6 300
6 300
6 300
I'm trying to roll-up the sum from the leaf node up to the hierarchy so it would return as ..
ID Name Sum
1 John 800
2 Mike 200
3 Erin 200
4 Janie 400
5 Eric 900
6 Peter 900
Any ideas how to achieve this in sql 2008? Thanks in advance.
EDIT All aggregation moved out of the CTE
WITH
tree AS
(
SELECT
id AS root_id,
name AS root_name,
id AS leaf_id
FROM
yourTreeTable
UNION ALL
SELECT
tree.root_id AS root_id,
tree.name AS root_name,
yourTreeTable.id AS leaf_id
FROM
tree
INNER JOIN
yourTreeTable
ON tree.leaf_id = yourTreeTable.ParentID
)
SELECT
tree.root_id,
tree.root_name,
COALESCE(SUM(yourScoresTable.score), 0) AS total
FROM
tree
LEFT JOIN
yourScoresTable
ON yourScoresTable.ID = tree.leafID
GROUP BY
tree.root_id,
tree.root_name
Here it is:
Let's supose this schema:
​create table #parent (
ID int,
ParentID int,
Name varchar(50) );
create table #child (
ID int,
Sales int );
The query is self explained:
WITH
tree AS
(
SELECT
id as id_parent,
id as id
FROM
#parent
UNION ALL
SELECT
tree.id_parent as id_parent,
#parent.id AS id
FROM
tree
INNER JOIN
#parent
ON tree.id = #parent.ParentID
)
SELECT
#parent.id,
#parent.name,
COALESCE(SUM(#child.Sales), 0) AS total
FROM
#parent
LEFT JOIN
tree
ON #parent.ID = tree.id_parent
LEFT JOIN
#child on tree.id = #child.id
GROUP BY
#parent.id,
#parent.name
CTE returns a list of 'leafs' for each employee (#parent), then query sums all sales for this 'leafs'. You can test it running.
EDITED
Query is fixed.

How to find all 'related' parents from a table containing set of (parent, child)?

I have a SQL Server 2005 table as follows:
parent child
1 a
2 a
2 b
3 b
3 c
4 c
5 d
6 d
7 d
8 e
9 e
9 f
10 f
Each parent can have one or more child, each child can also have one or more parents.
How can I find (or group) all parents that are related?
In above case, I want to group:
parent 1, 2, 3, 4 into group 1
parent 5, 6, 7 into group 2
parent 8, 9, 10 into group 3
A result from the query would look like: (Doesn't matter which parent use as group as long as it is from one of those parent in the group, min ID, or max ID would do)
parent child parent_group
1 a 1
2 a 1
2 b 1
3 b 1
3 c 1
4 c 1
5 d 5
6 d 5
7 d 5
8 e 8
9 e 8
9 f 8
10 f 8
This is similar to those standard boss - subordinate recursive SQL questions except the subordinate may have more than 1 boss.
Is it possible to use SQL to generate result as above? If so how?
Appreciate any help.
There is no way to do this in a single query. I found a blog post (with some ruby code) describing how you can calculate connected components in SQL (MySQL flavor).
Based on the article, I wrote the following SQL (SQL Server flavor):
-- Step 1: Create temporary tables
CREATE TABLE #items (
id int PRIMARY KEY,
component_id int
);
CREATE TABLE #links (
first int,
second int,
PRIMARY KEY (first, second)
);
CREATE TABLE #components_to_merge (
component1 int,
component2 int
PRIMARY KEY (component1, component2)
);
-- Step 2: Populate tables
INSERT INTO #items (id, component_id)
SELECT DISTINCT parent, parent
FROM children;
INSERT INTO #links (first, second)
SELECT DISTINCT c1.parent, c2.parent
FROM children c1
INNER JOIN children c2 ON c1.child = c2.child
WHERE c1.parent <> c2.parent;
-- Step 3: Merge components
WHILE 1 = 1 BEGIN
-- Step 3.1: Update #components_to_merge
TRUNCATE TABLE #components_to_merge;
INSERT INTO #components_to_merge (component1, component2)
SELECT DISTINCT t1.component_id, t2.component_id
FROM #links l
INNER JOIN #items t1 ON t1.id = l.first
INNER JOIN #items t2 ON t2.id = l.second
WHERE t1.component_id <> t2.component_id;
INSERT INTO #components_to_merge (component1, component2)
SELECT component2, component1
FROM #components_to_merge m1
WHERE component2 NOT IN (
SELECT m2.component2
FROM #components_to_merge m2
WHERE m2.component1 = m1.component1
);
IF (SELECT COUNT(*) FROM #components_to_merge) = 0
BREAK;
-- Step 3.2: Update #items
UPDATE i
SET component_id = target
FROM #items i
INNER JOIN (
SELECT
component1 AS source,
MIN(component2) AS target
FROM #components_to_merge
GROUP BY component1
) new_components
ON source = component_id
WHERE target < component_id;
END;
-- Step 4: Generate result
SELECT parent, child, component_id
FROM children
INNER JOIN #items ON id = parent;
You could wrap this up in a stored procedure:
CREATE PROCEDURE CalculateComponents AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
-- SQL code from above
ROLLBACK TRANSACTION;
END;
And then call it with
EXEC CalculateComponents;
Output:
parent child component_id
1 a 1
2 a 1
2 b 1
3 b 1
3 c 1
4 c 1
5 d 5
6 d 5
7 d 5
8 e 8
9 e 8
9 f 8
10 f 8