SQL Server - Concatenate all child parent relationships into a single string - sql

The problem:
I have a table that has family trees that has parents and child elements. What I need to return is the family tree for each child. I cant figure out how to do this with a cte or alike
Table:
ID
Name
Parent
1
Child1
2
2
parent1
3
3
Grandparent1
null
4
Child2
5
5
parent2
null
6
Child3
null
Expected results:
ID
Family
1
grandparent1, parent1
2
grandparent1
3
null
4
parent2
5
null
6
null

This is basically a recursive CTE. The tricky part is removing the current name from the family list.
The following approach uses the recursive CTE to generate all family members and then returns the current name. If the names can overlap significantly (such as "grandchild1" and "child1"), then it might need to be tweaked. But it works for your example:
with cte as (
select id, name, convert(varchar(max), ',' + name) as family, 1 as lev
from t
where parent is null
union all
select t.id, t.name, concat(cte.family, ',', t.name), lev + 1
from cte join
t
on t.parent = cte.id
)
select id, name,
stuff(nullif(replace(family, ',' + name, ''), ''), 1, 1, '') as familh
from (select cte.*, max(lev) over (partition by id) as max_lev
from cte
) cte
where lev = max_lev;
Here is a little db<>fiddle.

Related

Getting different Hierarchy levels and Path in Snowflake SQL

I am trying to replicate the result of a qlik function called Hierarchy. It creates the Hierarchy with different Levels and also gives the Hierarchy Path. The Code that i am using so far is giving me the Levels but with an error that Level2 values are also coming in Level1.
CREATE OR REPLACE TRANSIENT TABLE "Hierarchy" ( "NodeID" VARCHAR, "ParentID" VARCHAR, "NodeName" String)
Insert into "Hierarchy"("NodeID", "ParentID","NodeName")
VALUES
('1','4','London'),
('2','3','Munich'),
('3','5','Germany'),
('4','5','UK'),
('5','', 'Europe');
with recursive CteTree as
(Select "NodeID","ParentID","NodeName" as "NodeName1",
CAST (NULL AS varchar(255)) as "NodeName2",
CAST (NULL AS varchar(255)) as "NodeName3",
0 as NodeName
from "Hierarchy"
Where "ParentID" is not null
UNION ALL
Select child."NodeID", child."ParentID", "NodeName1",
Case When NodeName+1 = 1 then "NodeName" else "NodeName2" end,
Case When NodeName+1 = 2 then "NodeName" else "NodeName3" end,
NodeName+1
from CteTree
Join "Hierarchy" child
ON child."ParentID" = CteTree."NodeID"
)
select distinct * from CteTree order by "NodeName1"
The Output that it is producing:
Desired OutPut:
How can it be achieved?
with Hierarchy (NodeID, ParentID,NodeName) as (select * from VALUES
('1','4','London'),
('2','3','Munich'),
('3','5','Germany'),
('4','5','UK'),
('5','', 'Europe'))
select
sys_connect_by_path(NodeName, ' -> ') path
, NodeID
, ParentID
, NodeName
from
Hierarchy
START WITH ParentID =''
CONNECT BY PRIOR NodeID = ParentID ;
CREATE TABLE HIERARCHYWITHLEVEL(NODEID, PARENTID, NODENAME, LEVEL)
AS
WITH TREE AS
(SELECT NODEID, PARENTID, NODENAME, 1 AS LEVEL
FROM HIERARCHY
WHERE PARENTID = ''
UNION ALL
SELECT HIERARCHY.NODEID, HIERARCHY.PARENTID, HIERARCHY.NODENAME, LEVEL + 1
FROM HIERARCHY
JOIN TREE
ON HIERARCHY.PARENTID = TREE.NODEID
)
SELECT NODEID, PARENTID, NODENAME, LEVEL
FROM TREE;
NODEID
PARENTID
NODENAME
LEVEL
5
Europe
1
3
5
Germany
2
4
5
UK
2
2
3
Munich
3
1
4
London
3

SQL: Query complete hierarchy based on a primary key

We have a table like below
folderid name parent
==========================
1 one null
2 two 1
3 three 2
4 four 3
5 five 4
6 six 5
Is there a way to retrieve the complete list of records when given a folderid. For example if 1 is passed it should return the complete hierarchy till the leaf that is 6. If 6 is passed it should return the complete hierarchy till the root that is 1. If 4 is passed it should return the complete hierarchy from root to the leaf that is from 1 to 6.
You can use a recursive CTE:
with cte as (
select folderid
from t
where folderid = 1
union all
select t.folderid
from cte join
t
on cte.folderid = t.parent
)
select *
from cte
option (maxrecursion 0);
If you want additional columns, you can either include them in the recursive CTE or you can join them in the outer query.
Here is a db<>fiddle.
EDIT:
If you want to walk up and down the tree, I would recommend two CTEs:
with cte_c as (
select folderid, 1 as lev
from t
where folderid = 4
union all
select t.folderid, lev + 1
from cte_c join
t
on cte_c.folderid = t.parent
),
cte_p as (
select parent, 1 as lev
from t
where folderid = 4
union all
select t.parent as folderid, lev + 1
from cte_p join
t
on cte_p.parent = t.folderid
where t.parent is not null
)
select folderid
from cte_c
union all
select parent
from cte_p
where parent is not null
option (maxrecursion 0);
Here is a db<>fiddle for this version.

Lazy upward recursive query in sql

I'm trying to generate a path from the name of an item's parents. For example if test has for parent dad the path would be dad/test; and if dad had for parent gran the path of test would be gran/dad/test.
I only have the id of the child, so far I only have a query which generates the paths of everyone recursively and then selects the right one but that doesn't really seem efficient.
WITH SubItems
AS (
SELECT CAST([Name] AS VARCHAR(255)) AS [Path],
Id,
ParentId,
0 AS Depth
FROM Items
WHERE Id = 1 -- First parent of everyone
UNION ALL
SELECT CAST(CONCAT(parent.[Path], '/', sub.[Name]) AS VARCHAR(255)),
sub.Id,
sub.ParentId,
parent.Depth + 1
FROM Items sub
JOIN SubItems parent ON parent.Id = sub.ParentId
)
SELECT [Path]
FROM SubItems
WHERE Id = 1425 -- SubItem I want the path of
I can also go upwards, which would be faster but I can't create the path this way. I could try to concatenate all the results ordered by the "depth" but again this doesn't seem right.
DECLARE #Path;
WITH ParentItems
AS (
SELECT [Name],
Id,
ParentId,
0 AS Depth
FROM Items
WHERE Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT [Name],
parent.Id,
parent.ParentId,
sub.Depth - 1
FROM Items parent
JOIN ParentItems sub ON sub.ParentId = parent.Id
)
SELECT #Path = COALESCE(#Path + '/', '') + [Name]
FROM ParentItems
ORDER BY Depth;
SELECT #Path;
Is there a way to go upwards recursively?
Something like this for example, where ParentPath would be equal to CONCAT(ParentPath, '/', [Path]) again:
WITH ...
SELECT CONCAT(ParentPath, '/', [Name])
FROM Items
I know in C# you could do something like:
function getPath() {
return (parent?.getPath() ?? "") + "/" + this.Name;
}
Edit: Why I can't construct the path going up, like this:
WITH ParentItems AS (
SELECT i.Name, i.Id, i.ParentId, 0 AS Depth,
CONVERT(VARCHAR(MAX), i.Name) as path
FROM Items i
WHERE i.Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT i.Name, i.Id, i.ParentId, pi.Depth - 1,
CONCAT(pi.Name, '/', i.[Path])
FROM Items i JOIN
ParentItems pi
ON pi.ParentId = parent.Id
)
SELECT *
FROM ParentItems
ORDER BY Depth;
Assuming the example from above where gran is parent to dad is parent to test, the result of this query would be:
| name | path |
|------|---------------|
| gran | gran/dad/test |
| dad | dad/test |
| test | test |
While it should be the opposite:
| name | path |
|------|---------------|
| gran | gran/ |
| dad | gran/dad |
| test | gran/dad/test |
This is because of the way the query passes the name of the child upwards, adding it to the path of its parent rather than the opposite.
Why can't you construct the path going up?
WITH ParentItems AS (
SELECT i.Name, i.Id, i.ParentId, 0 AS Depth,
CONVERT(VARCHAR(MAX), i.Name) as path
FROM Items i
WHERE i.Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT i.Name, i.Id, i.ParentId, pi.Depth + 1,
CONCAT(i.Name, '/', pi.path)
FROM Items i JOIN
ParentItems pi
ON pi.ParentId = parent.Id
)
SELECT *
FROM ParentItems
ORDER BY Depth DESC;
For future references, using FOR XML PATH('') seems to work.
WITH ParentItems
AS (
SELECT [Name],
Id,
ParentId,
0 AS Depth
FROM Items
WHERE Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT [Name],
parent.Id,
parent.ParentId,
sub.Depth - 1
FROM Items parent
JOIN ParentItems sub ON sub.ParentId = parent.Id
)
SELECT (
SELECT '/' + [Name]
FROM ParentItems
ORDER BY Depth
FOR XML PATH('')
)
The following code:
Walks the tree from the child up to the oldest ancestor while assembling a path.
Gets the path to the oldest ancestor and splits it into individuals.
Walks the list of individuals from the oldest ancestor back down to the starting child while assembling the path.
NB: This code does not use String_Split because it is documented thusly: "The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string." A Jeff Moden string splitter is used which guarantees the order of the results.
Note that you can select the results of any of the intermediate CTEs in order to see how the process proceeds. Just replace the final select statement with one of the alternatives provided in comments.
Confession: I didn't try to generate the curious dangling solidus in the first row of the desired output ("gran/") rather than the more consistent "gran". It is assumed to be a typographical error in the sample data.
-- Sample data.
declare #Samples as Table ( Id Int Identity, Name VarChar(10), ParentName VarChar(10) );
insert into #Samples ( Name, ParentName ) values
( 'test', 'dad' ),
( 'dad', 'gran' ),
( 'gran', null );
select * from #Samples;
-- Starting point.
declare #ChildName as VarChar(10) = 'test';
-- Walk the tree.
with
Tree as (
-- Note that paths in this initial tree are built using Id , not Name .
-- This keeps the path length down, ensures rows are uniquely identified, avoids problems with "funny" names, ... .
-- Start at the target child name.
select Id, Name, ParentName, 0 as Depth,
Cast( Id as VarChar(100) ) as Path
from #Samples
where Name = #ChildName
union all
-- Walk up the tree one level at a time.
select S.Id, S.Name, S.ParentName, T.Depth + 1,
Cast( Cast( S.Id as VarChar(100) ) + '/' + T.Path as VarChar(100) )
from Tree as T inner join
#Samples as S on S.Name = T.ParentName
),
TreePath as (
-- Take the path of the oldest ancestor and split it apart.
select ItemNumber, Cast( Item as Int ) as Item from Tree as T cross apply
dbo.DelimitedSplit8K( T.Path, '/' ) where T.ParentName is NULL ),
InvertedTree as (
-- Start at the first item on path, i.e. the oldest ancestor.
select S.Name, 1 as Depth,
Cast( S.Name as VarChar(100) ) as Path
from TreePath as TP inner join
#Samples as S on S.Id = TP.Item
where TP.ItemNumber = 1
union all
-- Add chldren on the way down.
select S.Name, IT.Depth + 1,
Cast( IT.Path + '/' + S.Name as VarChar(100) )
from InvertedTree as IT inner join
TreePath as TP on TP.ItemNumber = IT.Depth + 1 inner join
#Samples as S on S.Id = TP.Item
)
-- To see the intermediate results use one of the following select statements:
-- select * from Tree;
-- select * from TreePath;
-- select * from InvertedTree;
select Name, Path
from InvertedTree
order by Depth;
The Jeff Moden string splitter:
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter VARCHAR(16))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+ Len( #pDelimiter ) FROM cteTally t WHERE SUBSTRING(#pString,t.N, Len( #pDelimiter ) ) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1 ,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;

Return Query Based on Tree Structure

I am looking to return a query that shows exactly like this:
Root 1
--> Child 2
----->Child 3
Root 2
--> Child 4
---->Child 5
So the query should return Root 1 as One row, ---> Child 2 as another row. Assume n Levels, and "--->" format is placed for each child. Level is higher then "---->" increases.
My Table definition is
[NodeId, ParentId, Name, Level]
On SQL Server 2008 and above, you can use hierarchyId datatype to quickly achieve the desired sorting. You can use REPLICATE() to get the dashes.
;with cte as (
select NodeId, ParentId, Name, 0 Level, '/' + cast(NodeId as varchar(max)) + '/' Hier
from tbl1
where ParentId is null
union all
select t.NodeId, t.ParentId, t.Name, Level+1, Hier + cast(t.NodeId as varchar(max)) + '/'
from tbl1 t
join cte c on t.ParentId = c.NodeId
)
select case when level=0
then ''
else replicate('-',level*2) + '>' end + Name
from cte
order by cast(Hier as hierarchyid);
SQL Fiddle
On earlier SQL Server 2005, you can emulate the hierarchyId sorting using zero-padded strings:
;with cte as (
select NodeId, ParentId, Name, 0 Level, right(replicate('0',10)+cast(NodeId as varchar(max)),11) Hier
from tbl1
where ParentId is null
union all
select t.NodeId, t.ParentId, t.Name, Level+1, Hier + right(replicate('0',10)+cast(t.NodeId as varchar(max)),11)
from tbl1 t
join cte c on t.ParentId = c.NodeId
)
select case when level=0
then ''
else replicate('-',level*2) + '>' end + Name
from cte
order by Hier;

sql select puzzle: remove children when parent is filtered out

I have a table essentially:
name has_children parent_id row_id values0.....valuesn
parent 1 1 1
children 0 1 2
children 0 1 3
parent 0 4 4
parent 1 5 5
children 0 5 6
children 0 5 7
the values for the children can be different than the values for the parent.
i want some selects/joins that will filter the table on a value column (i.e. >10) and will return the parent (even if false for the filter) if one of it's children is true for the filter.
acceptable return:
parent=true all children=false, return just parent
parent=false >=1 children=true, return parent and all non-filtered child
i'm sure this has been thought about before but i don't have the faintest idea how to phrase the question to find a solution.
ANSI compliant. Each specific DBMS may have a faster implementation
select *
from tbl
where id in-- PARENTS of CHILDREN that match
( select parent_id from tbl
where values0 > 10 and has_children = 0)
or id in -- ONE CHILD ONLY
( select MIN(id) from tbl
where values0 > 10 and has_children = 0
group by parent_id)
or id in -- PARENTS
( select id from tbl
where values0 > 10 and has_children = 1)
Better written as a JOIN
select t.*
from
( select parent_id as ID from tbl
where values0 > 10 and has_children = 0
UNION
select MIN(id) from tbl
where values0 > 10 and has_children = 0
group by parent_id
UNION
select id from tbl
where values0 > 10 and has_children = 1) X
join tbl t on X.ID = t.ID
It is probably easiest to deal with this as two separate queries with a UNION between them.
Select the parent where all children are false under condition.
Select the parent and some child (MAX or MIN is probably easiest).