Retrieve descendant (path) of tree base of multiple values - sql

I have a self reference table to store hierarchical values in order to show up them in a TreeView or so on ,according to James Crowley article (Tree structures in ASP.NET and SQL Server)
Our table would look something like this:
Id ParentId Name Depth Lineage
1 NULL Root Node 0 /1/
2 1 Child A 1 /1/2/
3 1 Child B 1 /1/3/
4 1 Child C 1 /1/4/
5 2 Child D 2 /1/2/5/
To get path of a node (for example id=5) he suggest following query against table
SELECT *
FROM dfTree
WHERE (SELECT lineage
FROM dfTree
WHERE id = 5) LIKE lineage + '%'
result would be :
Id ParentId Name Depth Lineage
1 NULL Root Node 0 /1/
2 1 Child A 1 /1/2/
5 2 Child D 2 /1/2/5/
And Is Acceptable
But how about to have a result set when there are multiple IDs which i want to have their path ? therefore for example in above example instead of Id=5 i would like to pass multiple values something like this :
SELECT *
FROM dfTree
WHERE (SELECT lineage
FROM dfTree
WHERE id IN (5,6,8,9)) LIKE lineage + '%'
But the above statement make no sense and it is invalid sql server expression
How could i solve this problem ?
Thanks in advance

This query...
SELECT DISTINCT T2.*
FROM
(
SELECT lineage
FROM dfTree
WHERE id IN (4, 5)
) T1
JOIN dfTree T2
ON
T1.Lineage LIKE T2.Lineage + '%'
...returns the following result on your test data:
Id ParentId Name Depth Lineage
1 NULL Root Node 0 /1/
2 1 Child A 1 /1/2/
4 1 Child C 1 /1/4/
5 2 Child D 2 /1/2/5/
As you can see, all paths are "merged" together - for example, the path component Id=1 belongs to both the path: /1/4/ and the path: /1/2/5/, yet exists in the result set only once.
One the other hand, if you do need to distinguish between different paths, you'd need to do something like this:
SELECT T2.*, T1.Id LeafId
FROM
(
SELECT id, lineage
FROM dfTree
WHERE id IN (4, 5)
) T1
JOIN dfTree T2
ON
T1.Lineage LIKE T2.Lineage + '%'
Result:
Id ParentId Name Depth Lineage LeafId
1 NULL Root Node 0 /1/ 4
4 1 Child C 1 /1/4/ 4
1 NULL Root Node 0 /1/ 5
2 1 Child A 1 /1/2/ 5
5 2 Child D 2 /1/2/5/ 5
In this case, each path is identified by its leaf. This assumes there are no diamond-shaped dependencies (i.e. this is a real tree and not just any DAG); if there are, then you'd need to use T1.Lineage instead of T1.Id to identify the path.

If you are running SQL Server 2005 or higher you could move the subquery to a CTE:
; with cte as (
SELECT lineage
FROM dfTree
WHERE id IN (5,6,8,9)
)
SELECT d.*
FROM dfTree d
inner join cte on cte.lineage like d.lineage + '%'
Or just restructure the subquery:
SELECT d.*
FROM dfTree d
inner join (
SELECT lineage
FROM dfTree
WHERE id IN (5,6,8,9)
) s on s.lineage like d.lineage + '%'

Related

How to get the list of parent and Child in a Hierarchy using Oracle SQL

I have two tables, structures of the same is as below:
Table 1. Transactional data table
trx id.
1
2
3
4
5..etc
Table 2
table 2 has the parent child relationship as below .
id subject_id (Child) object_id (Parent)
1 2 1
2 3 1
3 4 1
4 5 1
Now using above tables, the expected output is as follow:
1
2
3
4
5
Please let me know how can I achieve the same. I need to get the details from the Table 1 along with parent and its all children in the hierarchy. I just have one level of hierarchy.
As you just have a one level hierarchy you can get it to work just ordering the results correctly. Try this one:
select obj.object_id, t.*
from
(
select
object_id,
object_id as parent_id
from table2
union
select
subject_id as object_id,
object_id as parent_id
from table2
) obj
inner join table1 t
on t.id = obj.object_id
order by
obj.parent_id,
case when obj.object_id = obj.parent_id then 0 else 1 end,
obj.object_id
;
I light variation on the comment of the #a_horse...
select * from table1
where id in
(select object_id from table2
union all
select subject_id from table2)
order by id;
as expected
1
2
3
4
5
If you want to constraint the parent, simple add WHERE predicates in the subquery.

How to obtain total children count for all parents in Oracle tree hierarchy?

I have an Oracle Tree hierarchy structure that is basically similar to the following table called MY_TABLE
(LINK_ID,
PARENT_LINK_ID,
STEP_ID )
with the following sample data within MY_TABLE:
LINK_ID PARENT_LINK_ID STEP_ID
-----------------------------------------------
A NULL 0
B NULL 0
AA A 1
AB A 1
AAA AA 2
BB B 1
BBB BB 2
BBBA BBB 3
BBBB BBB 3
Based on the above sample data, I need to produce a report that basically returns the total count of rows for all children
of both parent link IDs (top level only required), that is, I need to produce a SQL query that returns the following information, i.e.:
PARENT RESULT COUNT
----------------------------
A 3
B 4
So I need to rollup total children that belong to all (parent) link ids, where the LINK_IDs have a PARENT_LINK_ID of NULL
I think something like this:
select link, count(*)-1 as "RESULT COUNT"
from (
select connect_by_root(link_id) link
from my_table
connect by nocycle parent_link_id = prior link_id
start with parent_link_id is null)
group by link
order by 1 asc
Please try:
WITH parent(LINK_ID1, LINK_ID, asCount) AS
(
SELECT LINK_ID LINK_ID1, LINK_ID, 1 as asCount
from MY_TABLE WHERE PARENT_LINK_ID is null
UNION ALL
SELECT LINK_ID1, t.LINK_ID, asCount+1 as asCount FROM parent
INNER JOIN MY_TABLE t ON t.PARENT_LINK_ID = parent.LINK_ID
)
select
LINK_ID1 "Parent",
count(asCount)-1 "Result Count"
From parent
group by LINK_ID1;
SQL Fiddle Demo

tricky select statement

I have a table with categories, each category as an ID, a Name and a ParentID. The problem is that there are 3 levels, parent categories, sub categories and child categories.
I can extract parent categories with a simple SELECT and a WHERE ParentID IS NULL clause as such:
SELECT *
FROM Category
WHERE ParentID IS NULL
However, a WHERE ParentID IS NOT NULL clause will return both, sub categories as well as child categories.
I'm looking for a way to extract only sub categories, and only child categories.
Typically, for these sort of problems, it is better to use the Recursive Queries Using Common Table Expressions. Something like so:
;WITH CategoriesTree(CategoryID, CategoryName, ParentName, CategoryLevel)
AS
(
SELECT
c.ID,
c.Name,
CAST('No Parent' AS VARCHAR(50)) AS ParentName,
0 AS CategoryLevel
FROM #Categories c
WHERE c.ParentID IS NULL
UNION ALL
SELECT c.ID, c.Name, p.CategoryName, p.CategoryLevel + 1
FROM CategoriesTree p
INNER JOIN #Categories c ON c.ParentID = p.CategoryID
)
SELECT *
FROM CategoriesTree
Where CategoryLevel = some id;
SQL Fiddle Demo
This will give you:
CATEGORYID CATEGORYNAME PARENTNAME CATEGORYLEVEL
1 Root Cateogry No Parent 0
2 Sub Cateogry 1 Root Cateogry 1
3 Sub Cateogry 2 Root Cateogry 1
4 Sub Cateogry 3 Root Cateogry 1
8 sub Cateogry 1 of 3 Sub Cateogry 3 2
7 Sub Cateogry 1 of 2 Sub Cateogry 2 2
5 Sub Cateogry 1 of 1 Sub Cateogry 1 2
6 sub Cateogry 2 of 1 Sub Cateogry 1 2
How does this work?
Using this query, you can control what level of categories you want to select. For instance, for the sample data, I used in the previous demo, here is the categories tree:
1: RootCategory Category Level: 0
|
|
----------------------------
| | |
| | |
2: Sub1 3: Sub2 4: sub3 Category Level: 1
| | |
------------ | |
| | | |
| | | |
5: Sub1of1 6: Sub2of1 7: sub1of2 8: sub1of3 Category Level: 2
This query will give you this categories tree with the new generated CategoryLevel column.
Note that: In the sample data I used in the demo, there was only one parent category (the categories with parentid IS NULL). However, the query will for work fine in case there were a lot of parent categories. And this because of the anchor query of the CTE, which is:
SELECT
c.ID,
c.Name,
CAST('No Parent' AS VARCHAR(50)) AS ParentName,
0 AS CategoryLevel
FROM #Categories c
WHERE c.ParentID IS NULL;
Then, you can use the generated column CategoryLevel to select only the child categories of the level that you are interested in.
For example, if you need to select only the sub categories of the first sub categories of the root category, you can get these categories using the predicate CategoryLevel = 2:
;WITH CategoriesTree(CategoryID, CategoryName, ParentName, CategoryLevel)
AS
(
...
)
SELECT *
FROM CategoriesTree
WHERE CategoryLevel = 2;
This will give you:
CATEGORYID CATEGORYNAME PARENTNAME CATEGORYLEVEL
8 sub Cateogry 1 of 3 Sub Cateogry 3 2
7 Sub Cateogry 1 of 2 Sub Cateogry 2 2
5 Sub Cateogry 1 of 1 Sub Cateogry 1 2
6 sub Cateogry 2 of 1 Sub Cateogry 1 2
SQL Fiddle Demo
First level categories - you have it:
SELECT *
FROM Category
WHERE ParentID IS NULL
For the second level categories you can try:
SELECT * FROM Category
WHERE ParentID IN (SELECT ID FROM Category WHERE ParentID IS NULL).
For the third:
SELECT * FROM Category
WHERE ParentID IN (SELECT ID FROM Category WHERE ParentID IS NOT NULL)
(Not tested)
How about something like:
-- Root parents
select c.* from categories c where c.ParentID is null
-- Second level. Select where parentid is a root category.
select c.* from categories c
where c.ParentID in (select c1.ID from categories c1 where c1.ParentID is null);
-- Third level. Select where parentid is a second level category
with second_level_cats (ID) as (
select c.ID from categories c
where c.ParentID in (select c1.ID from categories c1 where c1.ParentID is null)
)
select c.* from categories c
where c.ParentID in (select l2.ID from second_level_cats l2)
May not be entirely optimal but it seems to work. If there's only a relatively low number of rows and you're only ever going to go to three levels then it should suffice.

SQL select on parent child relation with multiple join conditions

I have a parent/child relation with the following child table:
CHILD_ID PARENT_ID CHILD_VALUE
------------------------------
1 1 x
2 1 y
3 2 y
Now I want to select all existing distinct parents where the CHILD_VALUE is:
child has value = x (there will be only 1 or none)
if no child with value x exists - NULL
In other words show all parents with "matching" child or NULL if no child "matching" the value exists.
So the result shall look like this:
PARENT_ID CHILD_ID CHILD_VALUE
------------------------------
1 1 x
2 NULL NULL
Question would be how I can narrow down the join in this case.
This does the trick:
select distinct a.parent_id, b.child_id, b.child_value
from test1 a
left outer join test1 b
on a.parent_id = b.parent_id
and b.child_value = 'x'
See also this SQL fiddle.

Joining other tables in oracle tree queries

Given a simple (id, description) table t1, such as
id description
-- -----------
1 Alice
2 Bob
3 Carol
4 David
5 Erica
6 Fred
And a parent-child relationship table t2, such as
parent child
------ -----
1 2
1 3
4 5
5 6
Oracle offers a way of traversing this as a tree with some custom syntax extensions:
select parent, child, sys_connect_by_path(child, '/') as "path"
from t2
connect by prior parent = child
The exact syntax is not important, and I've probably made a mistake in the above. The
important thing is that the above will produce something that looks like
parent child path
------ ----- ----
1 2 /1/2
1 3 /1/3
4 5 /4/5
4 6 /4/5/6
5 6 /5/6
My question is this: is it possible to join another table within the sys_connect_by_path(), such as the t1 table above, to produce something like:
parent child path
------ ----- ----
1 2 /Alice/Bob
1 3 /Alice/Carol
... and so on...
In your query, replace T2 with a subquery that joins T1 and T2, and returns parent, child and child description. Then in the sys_connect_by_path function, reference the child description from your subquery.
Based on Mike McAllister's idea, the following uses a derived table to achieve the desired result:
select
T.PARENT
,T.CHILD
,sys_connect_by_path(T.CDESC, '/')
from
(
select
t2.parent as PARENT
,t2.child as CHILD
,t1.description as CDESC
from
t1, t2
where
t2.child = t1.id
) T
where
level > 1 and connect_by_isleaf = 1
connect by prior
T.CHILD = T.PARENT
In my problem, all the parents are anchored under a "super-parent" root, which means that the paths can be fully described with SYS_CONNECT_BY_PATH, thereby obviating the need for cagcowboy's technique of concatenating the parent with the path.
SELECT parent, child, parents.description||sys_connect_by_path(childs.description, '/') AS "path"
FROM T1 parents, T1 childs, T2
WHERE T2.parent = parents.id
AND T2.child = childs.id
CONNECT BY PRIOR parent = child