How to self JOIN recursively in SQL? - sql

I have a table:
Series
========
ID
SeriesName
ParentSeriesID
A series can be a "root" series, (ParentSeriesID is 0 or null) or it can have a Parent. A series can also be several levels down, i.e. its Parent has a Parent, which has a Parent, etc.
How can I query the table to get a Series by it's ID and ALL descendant Series' ?
So far I have tried:
SELECT child.*
FROM Series parent JOIN Series child ON child.ParentSeriesID = parent.ID
WHERE parent.ID = #ParentID
But this only returns the first level of children, I want the parent node, and all "downstream" nodes. I am not sure how to progress from here.

If you are using SQL Server 2005+, you can use common-table expressions
With Family As
(
Select s.ID, s.ParentSeriesId, 0 as Depth
From Series s
Where ID = #ParentID
Union All
Select s2.ID, s2.ParentSeriesId, Depth + 1
From Series s2
Join Family
On Family.ID = s2.ParentSeriesId
)
Select *
From Family
For more:
Recursive Queries Using Common Table Expressions

I just enhance the work of Thomas. If you need to get the depth of the hierarchy and getting the parentid here is the code.
This was almost the same with Thomas' work.
With Family As
(
Select s.ID, s.ParentSeriesId, 0 as Depth
From Series s
Where ID = #ParentID <--- this was removed if you intend to get all hierarchy of the record. You can retain this if you want
Union All
Select s2.ID, s2.ParentSeriesId < --- change to **Family.ParentID**, Depth + 1
From Series s2
Join Family
On Family.ID = s2.ParentSeriesId
)
Select *
From Family
That's all. I know it's too late but I hope anyone who encounter this may help them. Thanks Thomas for the original code. :)

Make use of CTE feature avaiable in slq server 2005 onwards for recurisve query
USE AdventureWorks
GO
WITH Emp_CTE AS (
SELECT EmployeeID, ContactID, LoginID, ManagerID, Title, BirthDate
FROM HumanResources.Employee
WHERE ManagerID IS NULL
UNION ALL
SELECT e.EmployeeID, e.ContactID, e.LoginID, e.ManagerID, e.Title, e.BirthDate
FROM HumanResources.Employee e
INNER JOIN Emp_CTE ecte ON ecte.EmployeeID = e.ManagerID
)
SELECT *
FROM Emp_CTE
GO
You can view example over here :
SQL SERVER – Simple Example of Recursive CTE

Related

"Column "parent_id" for "cte" is specified more than once" in SQL recursive query

I have 5 SQL tables with columns as follows:
tbl_request_listEmpB
listEmpB_id request_id
tbl_request_listEmpD
listEmpD_id request_id
tbl_employee
id, parent_id (this one refers to id in tbl_department)
tbl_department
id, parent_id (that one referes to id of parent department)
tbl_department_manager
department_id, manager_employee_id
As input data I have employee_id and request_id.
I need to figure out whether the employee has access to the request (whether he's a manager or not)
Here's the query which is supposed to return 1 if the current user is a manager, 0 otherwise
with reqEmployees as (
select listEmpB_id as employee_id
from tbl_request_listEmpB
where request_id = ${request_id}
union all --concatenate the two tables
select listEmpD_id
from tbl_request_listEmpD
where request_id = ${request_id}
),
cte as (
select e.parent_id, null as parent_id
from reqEmployees r
join tbl_employee e on e.id = r.employee_id -- get these employees' departments
union all
select d.id, d.parent_id
from cte
join tbl_department d on d.id = cte.parent_id -- and get parent departments
)
select case when exists (select 1
from cte
join tbl_department_manager dm on dm.department_id = cte.id
where dm.manager_employee_id = ${employee_id})
then 1 else 0 end;
Finally, there's the logic that I believe is implemented in the query above:
First we need to identify whether the employee_id is a manager or not. If he is - find in which departments. So we query to tbl_department_manager based on manager_employee_id(=employee_id from input data) to get a list of corresponding department_id and store them in a variable. If the query returned 0 departments - terminate and return false
Based on request_id we collect ids of employees from both tbl_request_listEmpB and tbl_request_listEmpD. Later we refer to them as employee_id from reqEmployees
Query to tbl_employee based on ids retrieved from p.2 to get parent_id (list of unique departments employees belong to)
If there's a match between at least one department from p.1 and a one from p.3 return true
If not, there's a need to query to tbl_department and recursively search for a match between at least one element from p.1 and one element in p.3.parent_id
Here's what I mean
Consider the following chart
And here's the corresponding SQL table:
tbl_department (id, parent_id)
dep0 null
dep1 dep0
dep2 dep1
dep3 dep1
dep4 dep2
dep5 dep0
So, if we have a departments list returned from p.1 of ['dep1'] (there might be more than one element, we have to iterate through each element) we need to return true ONLY if from p.3 we've got dep1|dep2|dep3|dep4 - (dep1 descendants including dep1). If ['dep2'] return true if dep2|dep4. So there should at least one match of at least one element from p.1 and recursive result from p.5. I hope I illustrated it in the clearest way possible
Almost forgot - the query above gives me
"Column "parent_id" for "cte" is specified more than once"
But I don't think that it does what it's supposed to do, I need to rewrite it
Any help would be greatly appreciated
Without some sample data (and parameter values) and expected output for that data (with those parameter values), it's difficult to verify this solution.
I have assumed that your tbl_ou and tbl_department are in fact the same table.
Other than the CTE, it looks like the rest of your code should work. The CTE below now travels "both" directions through the hierarchy (upwards and downwards), finding both parents and children. Note that it only finds parents of parents and children of children, it doesn't find children of parents, for example, so no "siblings", "uncles" or whatever these records should be called!
You may need to cast both fields in the CTE seed record as the relevant data type. Based on the supplied data I have assumed that the datatype for department id (and therefore also for parent_id) is varchar(10).
cte as (
select
cast(e.parent_id as varchar(10)) as id,
cast(o.parent_id as varchar(10)) as parent_id,
0 as iteration
from
reqEmployees r
join tbl_employee e on e.id = r.employee_id
join tbl_department o on e.parent_id = o.id
--extra table here compared to earlier versions to allow us
--to traverse hierarchy in both directions
union all
select --This one finds "child" departments
o.id,
o.parent_id,
cte.iteration + 1
from
cte
join tbl_department o on o.id = cte.parent_id
where
cte.iteration >=0 --prevents siblings/uncles etc
union all
select --This one finds "parent" departments
o.id,
o.parent_id,
cte.iteration - 1
from
cte
join tbl_department o on o.parent_id = cte.id
where
cte.iteration <=0 --prevents siblings/uncles etc
)
You can test my script using this SQL Fiddle (updated).

Hierarchical query with join. Performance issue

I am trying to form a query that fetch the details of subordinates under multiple level of supervisors. The hierarchy is as follows:
General manager- regional manager- district manager - supervisor - employee
My requirement is that, any manager in this hierarchy should be able to see the details of the employee at the bottom of the hierarchy.
So far I have tried the following options:
1: 102 seconds to execute
WITH w AS
( select personId from assignments start with supervisorId = :inputId
Connect by prior personId = supervisorId)
Select columns FROM a,b
where a.column = b.column
And a.personId = w.personId;
2: 92 seconds
Select columns FROM a,b
where a.column = b.column
And a.personId IN ( select personId from assignments start with supervisorId = :inputId
Connect by prior personId = supervisorId);
3:0.75 seconds[no data fetched]
WITH w AS ( select columns FROM a,b
where a.column = b.column)
Select w.columns from w, assignments
Where t.personId = assignments.personId
Start with supervisorId = :inputId
Connect by prior personId = supervisorId.
In option 3, since join condition is executed first before connect by, that could be why it does not return any rows. Please note that when fetching data for a single employee, it takes less than a second to execute, ie by removing the hierarchical query part and the assignments table and giving personId of a single person.
So I am looking for an efficient solution to meet this requirement. Any method, even not including hierarchical queries are welcome.
Thanks,
Anoop

SQL Server: querying hierarchical and referenced data

I'm working on an asset database that has a hierarchy. Also, there is a "ReferenceAsset" table, that effectively points back to an asset. The Reference Asset basically functions as an override, but it is selected as if it were a unique, new asset. One of the overrides that gets set, is the parent_id.
Columns that are relevant to selecting the heirarchy:
Asset: id (primary), parent_id
Asset Reference: id (primary), asset_id (foreignkey->Asset), parent_id (always an Asset)
---EDITED 5/27----
Sample Relevent Table Data (after joins):
id | asset_id | name | parent_id | milestone | type
3 3 suit null march shape
4 4 suit_banker 3 april texture
5 5 tie null march shape
6 6 tie_red 5 march texture
7 7 tie_diamond 5 june texture
-5 6 tie_red 4 march texture
the id < 0 (like the last row) signify assets that are referenced. Referenced assets have a few columns that are overidden (in this case, only parent_id is important).
The expectation is that if I select all assets from april, I should do a secondary select to get the entire tree branches of the matching query:
so initially the query match would result in:
4 4 suit_banker 3 april texture
Then after the CTE, we get the complete hierarchy and our result should be this (so far this is working)
3 3 suit null march shape
4 4 suit_banker 3 april texture
-5 6 tie_red 4 march texture
and you see, the parent of id:-5 is there, but what is missing, that is needed, is the referenced asset, and the parent of the referenced asset:
5 5 tie null march shape
6 6 tie_red 5 march texture
Currently my solution works for this, but it is limited to only a single depth of references (and I feel the implementation is quite ugly).
---Edited----
Here is my primary Selection Function. This should better demonstrate where the real complication lies: the AssetReference.
Select A.id as id, A.id as asset_id, A.name,A.parent_id as parent_id, A.subPath, T.name as typeName, A2.name as parent_name, B.name as batchName,
L.name as locationName,AO.owner_name as ownerName, T.id as typeID,
M.name as milestoneName, A.deleted as bDeleted, 0 as reference, W.phase_name, W.status_name
FROM Asset as A Inner Join Type as T on A.type_id = T.id
Inner Join Batch as B on A.batch_id = B.id
Left Join Location L on A.location_id = L.id
Left Join Asset A2 on A.parent_id = A2.id
Left Join AssetOwner AO on A.owner_id = AO.owner_id
Left Join Milestone M on A.milestone_id = M.milestone_id
Left Join Workflow as W on W.asset_id = A.id
where A.deleted <= #showDeleted
UNION
Select -1*AR.id as id, AR.asset_id as asset_id, A.name, AR.parent_id as parent_id, A.subPath, T.name as typeName, A2.name as parent_name, B.name as batchName,
L.name as locationName,AO.owner_name as ownerName, T.id as typeID,
M.name as milestoneName, A.deleted as bDeleted, 1 as reference, NULL as phase_name, NULL as status_name
FROM Asset as A Inner Join Type as T on A.type_id = T.id
Inner Join Batch as B on A.batch_id = B.id
Left Join Location L on A.location_id = L.id
Left Join Asset A2 on AR.parent_id = A2.id
Left Join AssetOwner AO on A.owner_id = AO.owner_id
Left Join Milestone M on A.milestone_id = M.milestone_id
Inner Join AssetReference AR on AR.asset_id = A.id
where A.deleted <= #showDeleted
I have a stored procedure that takes a temp table (#temp) and finds all the elements of the hierarchy. The strategy I employed was this:
Select the entire system heirarchy into a temp table (#treeIDs) represented by a comma separated list of each entire tree branch
Get entire heirarchy of assets matching query (from #temp)
Get all reference assets pointed to by Assets from heirarchy
Parse the heirarchy of all reference assets
This works for now because reference assets are always the last item on a branch, but if they weren't, i think i would be in trouble. I feel like i need some better form of recursion.
Here is my current code, which is working, but i am not proud of it, and I know it is not robust (because it only works if the references are at the bottom):
Step 1. build the entire hierarchy
;WITH Recursive_CTE AS (
SELECT Cast(id as varchar(100)) as Hierarchy, parent_id, id
FROM #assetIDs
Where parent_id is Null
UNION ALL
SELECT
CAST(parent.Hierarchy + ',' + CAST(t.id as varchar(100)) as varchar(100)) as Hierarchy, t.parent_id, t.id
FROM Recursive_CTE parent
INNER JOIN #assetIDs t ON t.parent_id = parent.id
)
Select Distinct h.id, Hierarchy as idList into #treeIDs
FROM ( Select Hierarchy, id FROM Recursive_CTE ) parent
CROSS APPLY dbo.SplitIDs(Hierarchy) as h
Step 2. Select the branches of all assets that match the query
Select DISTINCT L.id into #RelativeIDs FROM #treeIDs
CROSS APPLY dbo.SplitIDs(idList) as L
WHERE #treeIDs.id in (Select id FROM #temp)
Step 3. Get all Reference Assets in the branches
(Reference assets have negative id values, hence the id < 0 part)
Select asset_id INTO #REFLinks FROM #AllAssets WHERE id in
(Select #AllAssets.asset_id FROM #AllAssets Inner Join #RelativeIDs
on #AllAssets.id = #RelativeIDs.id Where #RelativeIDs.id < 0)
Step 4. Get the branches of anything found in step 3
Select DISTINCT L.id into #extraRelativeIDs FROM #treeIDs
CROSS APPLY dbo.SplitIDs(idList) as L
WHERE
exists (Select #REFLinks.asset_id FROM #REFLinks WHERE #REFLinks.asset_id = #treeIDs.id)
and Not Exists (select id FROM #RelativeIDs Where id = #treeIDs.id)
I've tried to just show the relevant code. I am super grateful to anyone who can help me find a better solution!
--getting all of the children of a root node ( could be > 1 ) and it would require revising the query a bit
DECLARE #AssetID int = (select AssetId from Asset where AssetID is null);
--algorithm is relational recursion
--gets the top level in hierarchy we want. The hierarchy column
--will show the row's place in the hierarchy from this query only
--not in the overall reality of the row's place in the table
WITH Hierarchy(Asset_ID, AssetID, Levelcode, Asset_hierarchy)
AS
(
SELECT AssetID, Asset_ID,
1 as levelcode, CAST(Assetid as varchar(max)) as Asset_hierarchy
FROM Asset
WHERE AssetID=#AssetID
UNION ALL
--joins back to the CTE to recursively retrieve the rows
--note that treelevel is incremented on each iteration
SELECT A.Parent_ID, B.AssetID,
Levelcode + 1 as LevelCode,
A.assetID + '\' + cast(A.Asset_id as varchar(20)) as Asset_Hierarchy
FROM Asset AS a
INNER JOIN dbo.Batch AS Hierarchy
--use to get children, since the parentId of the child will be set the value
--of the current row
on a.assetId= b.assetID
--use to get parents, since the parent of the Asset_Hierarchy row will be the asset,
--not the parent.
on Asset.AssetId= Asset_Hierarchy.parentID
SELECT a.Assetid,a.name,
Asset_Hierarchy.LevelCode, Asset_Hierarchy.hierarchy
FROM Asset AS a
INNER JOIN Asset_Hierarchy
ON A.AssetID= Asset_Hierarchy.AssetID
ORDER BY Hierarchy ;
--return results from the CTE, joining to the Asset data to get the asset name
---that is the structure you will want. I would need a little more clarification of your table structure
It would help to know your underlying table structure. There are two approaches which should work depending on your environment: SQL understands XML so you could have your SQL as an xml structure or simply have a single table with each row item having a unique primary key id and a parentid. id is the fk for the parentid. The data for the node are just standard columns. You can use a cte or a function powering a calculated column to determin the degree of nesting for each node. The limit is that a node can only have one parent.

Find root in a database tree-structured table using SQL

I have the following DB table describing a Bill Of Materials(BOM), basically a tree-structure:
Part(PartId, ParentId, PartName)
The Parts with ParentId = 0 are finished product, meaning they do not compose any other product.
Now given a PartId I would like to know to which products it belongs by using plain SQL (MS SQL Server) or LINQ lambda
Try the following:
;WITH CTE AS
(
SELECT PartId, ParentId
FROM Part
WHERE PartId = #PartId
UNION ALL
SELECT B.PartId, B.ParentId
FROM CTE A
INNER JOIN #Part B
ON A.ParentId = B.PartId
)
SELECT DISTINCT PartId
FROM CTE
WHERE ParentId = 0

Trace a Hierarchy in a Table

I have an "Employee" table with an "EmployeeID" column and a column representing Employee's
Boss (BossID) which in turn is an employee in the "Employee" table. How can I trace the hierarchy from a given "EmployeeID" to the top most Boss. I do not want a self join approach in this, also I am using SQL Server 2005.
Thank you
Manu
You have to use some sort of self join basically with the table structure you describe but can use a recursive CTE for this to handle arbitrary depths of hierarchy if that was the concern?
WITH cte AS
(
SELECT EmployeeID, BossId
FROM Employee where EmployeeID = #EmployeeID
UNION ALL
SELECT e.EmployeeID, e.BossId
FROM Employee e JOIN cte ON cte.BossId = e.EmployeeID
)
SELECT EmployeeID
FROM cte