Get all children and grandchildren from either of the two parent columns - sql

I need to find all the parent-children relationships, which are all linked to my primary column ID
I have tried the below thus far, but that works with only one column.
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE parent1 IS NULL
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = e.parent1
)
SELECT Name, Level, Path, Parent FROM tb

As i understand the parent can be in either parent1 or parent 2 column; then in such case your query should be as below
The only change is use of keyword COALSECE which takes the first non NULL value from the list.
bug assumption is that both parent1 and parent2 are not (Non-Null) together.
See live demo
;
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE COALESCE(parent1,parent2) IS NULL
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = COALESCE(e.parent1,e.parent2)
)
SELECT Name, Level, Path, Parent FROM tb

Related

Delete the same reference multiple columns rows safely?

I need to find all the parent-children relationships, which are all linked to my primary column ID
How I can delete the same reference columns in the table? Let say for example,if I want to delete "Google", I have to delete "HP" and Intel first also the child of HP as well.
I have tried the below thus far, but that works with only one column.
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE parent1 IS NULL
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = e.parent1
)
SELECT Name, Level, Path, Parent FROM tb
is this use full?
declare #tmp table (id int, Name varchar(10),Parent1 int,Parent2 int,Parent3 int,Parent4 int,Parent5 int)
insert into #tmp
SELECT 1,'Microsoft',NULL,NULL,NULL,NULL,NULL
union
SELECT 2,'Google',1,NULL,NULL,NULL,NULL
union
SELECT 3,'HP',NULL,2,NULL,NULL,NULL
union
SELECT 4,'Amazone',NULL,NULL,3,NULL,NULL
union
SELECT 5,'FB',NULL,NULL,NULL,4,NULL
union
SELECT 6,'Yahoo',NULL,NULL,NULL,4,NULL
union
SELECT 7,'Intel',NULL,NULL,2,NULL,NULL
union
SELECT 8,'Apple',7,5,NULL,NULL,NULL
select * from #tmp
;with name_tree as (
select *
from #tmp
where id = 2
union all
select c.*
from #tmp c
join name_tree p on (p.id = c.parent1 or p.id = c.parent2 or p.id = c.parent3 or p.id = c.parent4 or p.id = c.parent5)
)
delete from t
from #tmp t
JOIN name_tree c on t.id=c.id
select * from #tmp
I haven't provided an actual solution to your issue here. But I would recommend investigating recursive Common Table Expressions. That should allow you to find all the parent records, then you can run a delete on them.
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
You can simply modify the recursive CTE's where clause like in below query to get all rows that need to be deleted.
See live demo
create table krishtest (id int, name varchar(100), parent1 int, parent2 int)
insert into krishtest values
(1,'Microsoft', NULL, NULL),
(2,'Google',1,NULL),
(3,'HP',NULL,2),
(4,'amazon',3,NULL),
(5,'FB',NULL,4),
(6,'yahoo',3,NULL),
(7,'cisco',6,NULL)
;
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE -- COALESCE(parent1,parent2) IS NULL
name ='HP'
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = COALESCE(e.parent1,e.parent2)
)
--delete FROM krishtest where id in( select id from tb)
--select * from krishtest
SELECT Name, Level, Path, Parent FROM tb

Using CTE to assign level code to item how to retrieve the last item that already in the trail string

I am using the following query to assign level code to item.
;with C ( Ingredient_Item_No,
Lvl,
Trail)
as (
select
Matl.Ingredient_Item_No,
2 as Lvl,
CAST(('/' + Matl.Ingredient_Item_No + '/') as varchar(max)) as Trail
from Materials as Matl
and Matl.Product_Item_No = Lvl.Item_No
where Lvl.Level_Code = 1
union all
select
Matl.Ingredient_Item_No,
C.Lvl + 1,
C.trail + CAST((Matl.Ingredient_Item_No +'/') as varchar(max))
from NVA_Work_Rollup_BOM_Materials as Matl
inner join C
on Matl.Product_Item_No = C.Ingredient_Item_No
where CHARINDEX(CAST((Matl.Ingredient_Item_No + '/') as varchar(max)), C.trail) = 0
)
select * from C
The "Material" table structure is like this:
Product Ingredient
A B
B C
D E
E F
C A
The Level_Code have a list of item that already been assigned with level 1.
I use the trial column to store the ingredient hierarchy. Whenever the ingredient is already in the trail for the item, I will not assign it with another level. But that also means there is bad record that not supposed to be in the material table. For example, if I have A (1) -> B (2) -> C (3) -> A, A could not be the ingredient of the product C since it is a low level product. it also means that the pair (c (product), A (ingredient)) is a wrong record that need to be take out from 'Material' table. My problem is that I could use the trail to keep track the right order. But how could I retrieve the last pair that with wrong order, such as C -> A?
Edit:
Here is what table 'Lvl' Looks like
item_no level_Code
A 1
B 1
Any help will be appreciated!
Updated answer: Sorry, that was rather buggy.
With some horribly named Level i stuff:
-- Sample data.
declare #Material as Table ( Product VarChar(10), Ingredient VarChar(10) );
insert into #Material ( Product, Ingredient ) values
( 'A', 'B' ), ( 'B', 'C' ), ( 'D', 'E' ), ( 'E', 'F' ), ( 'C', 'A' );
--select * from #Material;
declare #Lvl as Table ( ImALevel1Product VarChar(10) );
insert into #Lvl ( ImALevel1Product ) values ( 'A' ), ( 'B' );
select *
from #Material as M left outer join
#Lvl as L on L.ImALevel1Product = M.Product;
declare #False as Bit = 0, #True as Bit = 1;
-- Run through the hierarchy looking for loops.
with Children as (
select Product, Ingredient,
Convert( VarChar(4096), '|' + Convert( VarChar(10), Product ) + '|' ) as Path, #False as Loop
from #Material
where Product in ( select ImALevel1Product from #Lvl )
union all
select Child.Product, Child.Ingredient,
Convert( VarChar(4096), Path + Convert( VarChar(10), Child.Product ) + '|' ),
case when Path like '%|' + Convert( VarChar(10), Child.Ingredient ) + '|%' then #True else #False end
from #Material as Child inner join
Children as Parent on Parent.Ingredient = Child.Product
where Parent.Loop = 0 )
select *
from Children
option ( MaxRecursion 0 )

How to create path with sql query

What is the best solution :
I have a table in sqlserver with these contents .
PARENT CHILD Level
A B 0
B C 1
C D 2
D E 3
I need a query to create this result :
A/B/C/D/E
You can use a recursive CTE for this:
with cte as (
select t.parent as p, t.parent as c, 0 as lev
from table t
where not exists (select 1 from t t2 where t2.child = t.parent)
union all
select cte.p, t.child, lev + 1
from cte join
table t
on cte.c = t.parent
)
select stuff((select '/' + cte2.c
from cte cte2
where cte2.p = cte.p
order by cte2.lev
for xml path ('')
), 1, 1, '') as path
from cte
group by cte.p;
Here is a SQL Fiddle.
Use Coalesce to combine rows into single column separated by '/' delimiter
DECLARE #path VARCHAR(8000) = (SELECT parent FROM test WHERE LEVEL = 0)
SELECT #path = COALESCE(rtrim(#path) + '/', '') + child FROM test
SELECT #path

SQL : How to get full hierarchy paths avoiding self & back relations?

I have the following table (StatusTransitions) :: SQL Fiddle
I use the below queries to get all full hierarchy paths ::
;WITH Paths AS
(
SELECT
ID, FromID, ToID,
CAST(FromID + ',' + CAST(ToID AS VARCHAR(100)) AS varchar(100)) AS [Path]
, 1 as LevelID
FROM StatusTransitions
WHERE FromID = 'A'
UNION ALL
SELECT
NextTransition.ID, NextTransition.FromID, NextTransition.ToID,
CAST(PreviousTransition.[Path] + ',' + CAST( NextTransition.ToID AS VARCHAR(100)) AS varchar(100)) AS [Path]
,(PreviousTransition.LevelID + 1) as LevelID
FROM
StatusTransitions AS NextTransition
join Paths AS PreviousTransition ON NextTransition.FromID = PreviousTransition.ToID
)
SELECT ID, FromID, ToID, [Path], LevelID
FROM Paths
WHERE ToID NOT IN
(
SELECT FromID
FROM StatusTransitions
WHERE FromID <> 'A'
)
Order By ID
OPTION (MAXRECURSION 20)
Query work perfectly in case we don't have any self relation between the same item or any back relation ( example :: items with id 12,13 ) ..
previous relations go into infinite loops ..
How can change this queries in which be able to avoid these relations ??
You prevent cycles by being sure that the new element in the path is not already in the path. The following where statement does this:
WHERE ','+PreviousTransition.[Path]+',' not like '%,'+NextTransition.ToID+',%'
The complete query is:
;WITH Paths AS
(
SELECT
ID, FromID, ToID,
CAST(FromID + ',' + CAST(ToID AS VARCHAR(100)) AS varchar(100)) AS [Path]
, 1 AS LevelID
FROM StatusTransitions
WHERE FromID = 'A'
UNION ALL
SELECT
NextTransition.ID, NextTransition.FromID, NextTransition.ToID,
CAST(PreviousTransition.[Path] + ',' + CAST( NextTransition.ToID AS VARCHAR(100)) AS varchar(100)) AS [Path],
(PreviousTransition.LevelID + 1) AS LevelID
FROM StatusTransitions NextTransition JOIN
Paths PreviousTransition
ON NextTransition.FromID = PreviousTransition.ToID
WHERE ','+PreviousTransition.[Path]+',' not like '%,'+NextTransition.ToID+',%'
)
SELECT ID, FromID, ToID, [Path], LevelID
FROM Paths
WHERE ToID NOT IN
(
SELECT FromID
FROM StatusTransitions
WHERE FromID <> 'A'
)
ORDER BY ID
OPTION (MAXRECURSION 20);
Because of the not in filtering, the result is not the same as the query on the original data, but I believe it is working correctly. The SQL Fiddle is here.

recursive query with peer relations

Let's say there is a table of relationships
(entity_id, relationship, related_id)
1, A, 2
1, A, 3
3, B, 5
1, C, null
12, C, 1
100, C, null
I need a query that will pull all related rows.
For example, if i queried for entity_id = 1, the following rows should be pulled
1, A, 2
1, A, 3
3, B, 5
1, C, null
12, C, 1
Actually, if i queried for entity_id = 1, 2, 3, 5, or 12, the resultset should be the same.
This is different than the standard manager-employee paradigm as there is no hierarchy. The relationships can go in any direction.
EDIT
None of the answers posted thus far worked.
I was able to come up with a solution that works.
I'll give the solution credit to the one who can clean this monstrosity into something more elegant.
with tab as (
-- union for reversals
select id, entity_id, r.related_id, 1 level
, cast('/' + cast(entity_id as varchar(1000)) + '/' as varchar(1000)) path
from _entity_relation r
where not exists(select null from _entity_relation r2 where r2.related_id=r.entity_id)
or r.related_id is null
union
select id, related_id, r.entity_id, 1 level
, cast('/' + cast(related_id as varchar(1000)) + '/' as varchar(1000)) path
from _entity_relation r
where not exists(select null from _entity_relation r2 where r2.related_id=r.entity_id)
or r.related_id is null
-- create recursive path
union all
select r.id, r.entity_id, r.related_id, tab.level+1
, cast(tab.path + '/' + cast(r.entity_id as varchar(100)) + '/' + '/' + cast(r.related_id as varchar(1000)) + '/' as varchar(1000)) path
from _entity_relation r
join tab
on tab.related_id = r.entity_id
)
select x.id
, x.entity_id
,pr.description as relation_description
,pt.first_name + coalesce(' ' + pt.middle_name,'') + ' ' + pt.last_name as relation_name
,CONVERT(CHAR(10), pt.birth_date, 101) as relation_birth_date
from (
select entity_id, MAX(id) as id from (
select distinct tab.id, entity_id
from tab
join(
select path
from tab
where entity_id=#in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
union
select distinct tab.id, related_id
from tab
join(
select path
from tab
where entity_id=#in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
union
select distinct tab.id, entity_id
from tab
join(
select path
from tab
where related_id=#in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
union
select distinct tab.id, related_id
from tab
join(
select path
from tab
where related_id=#in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
) y
group by entity_id
) x
join _entity_relation pr on pr.id = x.id
join _entity pt on pt.id = x.entity_id
where x.entity_id <> #in_entity_id;
Please be careful with you data as to accomplish your task you must avoid circular references. The following query can be optimized but for sure it'll work
;with tab as (
select entity_id, relationship, related_id, 1 level, cast('/' + cast(entity_id as varchar(1000)) as varchar(1000)) path
from #r r
where not exists(select null from #r r2 where r2.related_id=r.entity_id)
or r.related_id is null
union all
select r.entity_id, r.relationship, r.related_id, tab.level+1, cast(tab.path + '/' + cast(r.entity_id as varchar(100)) as varchar(1000)) path
from #r r
join tab
on tab.related_id = r.entity_id
)
select distinct tab.*
from tab
join(
select path
from tab
where entity_id=1) p
on p.path like tab.path + '%' or tab.path like p.path + '%'
Solution using two CTEs
I first created a table with relationships that go both ways and then created a recursive CTE that uses this both ways results to build whole hierarchies with ancestor paths...
with both as
(
select *, 0 as rev
from t
where related_id is not null
union
select *, 1
from t
),
recurs as
(
select *, cast('/' as varchar(100)) as anc
from both
where entity_id is null
union all
select b.*, cast(re.anc + cast(b.entity_id as varchar) + '/' as varchar(100))
from both b
join recurs re
on (re.related_id = b.entity_id)
where charindex('/'+cast(isnull(b.entity_id,'') as varchar)+'/', re.anc) = 0
)
select *
/*
THIS ONE SHOULD BE USED TO RETURN TO ORIGINAL
case when is_reverse = 1 then related_id else entity_id end as entity_id,
relationship,
case when is_reverse = 0 then related_id else entity_id end as related_id
*/
from recurs
where related_id = xXx or
charindex('/'+cast(xXx as varchar)+'/', anc) != 0
Replace xXx with actual value.
This query assumes that root element is the one with entity_id = null, so it builds the whole recursion from there. If that's not the case you'll have to change it accordingly.
I've added loop checks either loops are 1,2,3,4,5,1 or 1,2,3,4,5,3... So total or partial loops. Both will work.