How to recursively expand rows in sql server? - sql-server

In MS SQL Server, if I have 2 tables like so
Group
------------
ID GroupName
1 A
2 B
3 C
GroupMembership
---------------
Value GroupID Type
1 2 Node
3 2 Node
2 1 Group
4 1 Node
5 3 Node
In the membership table, if the type is Node, then it is just a normal value. But if its type is Group, then it is referring to the group defined in the Group table where the Value is the GroupID.
So the above querying for GroupID=1, would result in this kind of tree
4, (1, 3)
But in theory I could make any kind of tree with any amount of levels of nested brackets, by correctly setting up the group references between both tables.
The question is, how can I do a select statement for GroupID=1, such that it would recursively expand all the related records (even deeply nested ones) and return the membership records?
So ideally it would return
1 2 Node
3 2 Node
2 1 Group
4 1 Node
My current query only returns this as it doesn't know how to expand the rows recursively.
2 1 Group
4 1 Node
What is a simple sql query to do this?

You should use Common table expression with recursion:
DECLARE #GroupMembership TABLE
(
Value INT,
GroupID INT,
Type VARCHAR(5)
);
INSERT INTO #GroupMembership (Value, GroupID, Type )
VALUES (1 , 2 , 'Node' ),
(3 , 2 , 'Node' ),
(2 , 1 , 'Group'),
(4 , 1 , 'Node' ),
(5 , 3 , 'Node' );
DECLARE #requestingGroupID INT = 1;
WITH Recursion AS (
SELECT Value, GroupID, Type
FROM #GroupMembership
WHERE GroupID = #requestingGroupID
UNION ALL
SELECT M.Value, M.GroupID, M.Type
FROM Recursion AS R
INNER JOIN #GroupMembership AS M ON R.Value = M.GroupID AND R.Type = 'Group'
)
SELECT *
FROM Recursion
Result:
Value
GroupID
Type
2
1
Group
4
1
Node
1
2
Node
3
2
Node

Related

Classifying rows into a grouping column that shows the data is related to prior rows

I have a set of data that I want to classify into groups based on a prior record id existing on the newer rows. The initial record of the group has a prior sequence id = 0.
The data is as follows:
customer id
sequence id
prior_sequence id
1
1
0
1
2
1
1
3
2
2
4
0
2
5
4
2
6
0
2
7
6
Ideally, I would like to create the following grouping column and yield the following results:
customer id
sequence id
prior sequence id
grouping
1
1
0
1
1
2
1
1
1
3
2
1
2
4
0
2
2
5
4
2
2
6
0
3
2
7
6
3
I've attempted to utilize island gap logic utilizing the ROW_NUMBER() function. However, I have been unsuccessful in doing so. I suspect the need here is more along the lines of a recursive CTE, which I am attempting at the moment.
I agree that a recursive CTE will do the job. Something like:
WITH reccte AS
(
/*query that determines starting point for recursion
*
* In this case we want all records with no prior_sequence_id
*/
SELECT
customer_id,
sequence_id,
prior_sequence_id,
/*establish grouping*/
ROW_NUMBER() OVER (ORDER BY sequence_id) as grouping
FROM yourtable
WHERE prior_sequence_id = 0
UNION
/*join the recursive CTe back to the table and iterate*/
SELECT
yourtable.customer_id,
yourtable.sequence_id,
yourtable.prior_sequence_id,
reccte.grouping
FROM reccte
INNER JOIN yourtable ON reccte.sequence_id = yourtable.prior_sequence_id
)
SELECT * FROM reccte;
It looks like you could use a simple correlated query, at least given your sample data:
select *, (
select Sum(Iif(prior_sequence_id = 0, 1, 0))
from t t2
where t2.sequence_id <= t.sequence_id
) Grouping
from t;
See Example Fiddle

Finding an Hierarchy of row in SQL Server

I am unable to find the logic for below question in SQL Server.
I have a table like below.
id ParentID
---------------
1 NULL
2 NULL
3 1
4 2
5 3
6 5
I need a query which will return hierarchy of a row, like this:
Hierarchy id ParentID
----------------------------
1 1 NULL
1 2 NULL
2 3 1
2 4 2
3 5 3
4 6 5
I will explain the hierarchy:
For any row if ParentId is null then Hierarchy will be 1
Any row if ParentId is not null and ParentId's ParentId is null then 2
Any row if ParentId is not null, ParentId's ParentId is not null and next ParentId's ParentId is null then 3
And it goes on
How can write the query for this logic.
You can achieve this with a recursive query:
(in the below example your initial table data is stored in #a):
;With DATA AS (
SELECT 1 as hierarchy
,Id
,parentid
from #a
where parentid is null
UNION ALL
SELECT Data.Hierarchy + 1
,a.id
,a.parentid
FROM #a a
INNER JOIN DATA
ON Data.id = a.parentid
)
SELECT *
FROM DATA
ORDER BY hierarchy, Id

Roll up count in hierarchical table(self referential table)

I Have a table in the below format.This is a self referential table where each record points to its parent record.
NODE_ID PARENT_ID COUNT
1 0 NULL
2 1 NULL
3 2 10
4 2 12
5 0 NULL
6 5 NULL
7 6 NULL
8 7 12
I want the output to be in below format.The count of parent should be the sum of count of leaf childs.
Note: Only the leaf childs will contain the count. I want to roll it up till parents.
NODE_ID PARENT_ID COUNT
1 0 22
2 1 22
3 2 10
4 2 12
5 0 12
6 5 12
7 6 12
8 7 12
Please help.
Well, I couldn't think of anything simpler:
;WITH GetLevelsCTE AS (
SELECT NODE_ID, PARENT_ID, COUNT, level = 1, ROOT = NODE_ID
FROM mytable
WHERE PARENT_ID = 0
UNION ALL
SELECT t1.NODE_ID, t1.PARENT_ID, t1.COUNT, level = t2.level + 1, t2.ROOT
FROM mytable AS t1
JOIN GetLevelsCTE AS t2 ON t2.NODE_ID = t1.PARENT_ID
), MaxLevelCTE AS (
-- Get MAX level per root NODE_ID
SELECT MAX(level) AS max_level, ROOT
FROM GetLevelsCTE
GROUP BY ROOT
), GetCountCTE AS (
-- Anchor query: start from the bottom
SELECT t1.NODE_ID, t1.PARENT_ID, t1.COUNT, t1.level
FROM GetLevelsCTE AS t1
JOIN MaxLevelCTE AS t2 ON t1.ROOT = t2.ROOT
WHERE t1.level = t2.max_level
UNION ALL
-- Recursive query: get counts of next level
SELECT t1.NODE_ID, t1.PARENT_ID, t2.COUNT, t1.level
FROM GetLevelsCTE AS t1
JOIN GetCountCTE AS t2 ON t1.level = t2.level - 1 AND t1.NODE_ID = t2.PARENT_ID
)
SELECT NODE_ID, PARENT_ID, SUM(COUNT) AS COUNT
FROM GetCountCTE
GROUP BY NODE_ID, PARENT_ID
ORDER BY NODE_ID
Short explanation:
GetLevelsCTE is used to assign a level number to every node of the tree.
MaxLevelCTE uses the previous CTE in order the obtain the maximum level of the tree.
GetCountCTE uses both previous CTEs in order to traverse the tree from the bottom to the parent node. This way, the COUNT is propagated to the parent node.

Creating a recursive CTE with no rootrecord

My Apologies for the appalling Title, I was trying to be descriptive but not sure I got to the point. Hopefully the below will explain it
I begin with a table that has the following information
Party Id Party Name Party Code Parent Id
1 Acme 1 ACME1 1
2 Acme 2 ACME2 1
3 Acme 3 ACME3 3
4 Acme 4 ACME4 4
5 Acme 5 ACME5 4
6 Acme 6 ACME6 6
As you can see this isn't perfect for a recursive CTE because rather than having a NULL where there isn't a parent record it is instead parented to itself (see rows 1,3 and 6). Some however are parented normally.
I have therefore tried to amend this table in a CTE then refer to the output of that CTE as part of my recursive query... This doesn't appear to be running very well (no errors yet) so I wonder if I have managed to create an infinite loop or some other error that just slows the query to a crawl rather than killing it
My Code is below... please pick it apart!
--This is my attempt to 'clean' the data and set records parented to themselves as the 'anchor'
--record
WITH Parties
AS
(Select CASE
WHEN Cur_Parent_Id = Party_Id THEN NULL
ELSE Cur_Parent_Id
END AS Act_Parent_Id
, Party_Id
, CUR_PARTY_CODE
, CUR_PARTY_NAME
FROM EDW..TBDIMD_PARTIES
WHERE CUR_FLG = 1),
--In this CTE I referred to my 'clean' records from above and then traverse through them
--looking at the actual parent record identified
linkedParties
AS
(
Select Act_Parent_Id, Party_Id, CUR_PARTY_CODE, CUR_PARTY_NAME, 0 AS LEVEL
FROM Parties
WHERE Act_Parent_Id IS NULL
UNION ALL
Select p.Act_Parent_Id, p.Party_Id, p.CUR_PARTY_CODE, p.CUR_PARTY_NAME, Level + 1
FROM Parties p
inner join
linkedParties t on p.Act_Parent_Id = t.Party_Id
)
Select *
FROM linkedParties
Order By Level
From the data I supplied earlier the results I would expect are;
Party Id Party Name Party Code Parent Id Level
1 Acme 1 ACME1 1 0
3 Acme 3 ACME3 3 0
4 Acme 4 ACME4 4 0
6 Acme 6 ACME6 6 0
2 Acme 2 ACME2 1 1
5 Acme 5 ACME5 4 1
If everything seems to be OK then I'll assume its just a processing issue and start investigating that but I am not entirely comfortable with CTE's so wish to make sure the error is not mine before looking elsewhere.
Many Thanks
I think that you made it more complicated than it needs to be :).
drop table #temp
GO
select
*
into #temp
from (
select '1','Acme 1','ACME1','1' union all
select '2','Acme 2','ACME2','1' union all
select '3','Acme 3','ACME3','3' union all
select '4','Acme 4','ACME4','4' union all
select '5','Acme 5','ACME5','4' union all
select '6','Acme 6','ACME6','6'
) x ([Party Id],[Party Name],[Party Code],[Parent Id])
GO
;with cte as (
select
*,
[Level] = 0
from #temp
where 1=1
and [Party Id]=[Parent Id] --assuming these are root records
union all
select
t.*,
[Level] = c.[Level]+1
from #temp t
join cte c
on t.[Parent Id]=c.[Party Id]
where 1=1
and t.[Party Id]<>t.[Parent Id] --prevent matching root records with themselves creating infinite recursion
)
select
*
from cte
(* should ofcourse be replaced with actual column names)

SQL Server get parent list

I have a table like this:
id name parent_id
1 ab1 3
2 ab2 5
3 ab3 2
4 ab4 null
5 ab5 null
6 ab6 null
I need to do a query with input id = 1 (for an example) and results will be like this:
id name parent_id
5 ab5 null
2 ab2 5
3 ab3 2
1 ab1 3
(List all parents at all level start at item id = 1)
Something like this perhaps?
WITH parents(id,name,parent,level)
AS
(
SELECT
ID,
NAME,
PARENT,
0 as level
FROM
TABLE
WHERE ID = 1
UNION ALL
SELECT
ID,
NAME,
PARENT,
Level + 1
FROM
TABLE
WHERE
id = (SELECT TOP 1 parent FROM parents order by level desc)
)
SELECT * FROM parents
Use WITH RECURSIVE. Documentation and adaptable example: Recursive Queries Using Common Table Expressions.

Resources