Recursive Select in SQL

Recursive select in SQL

declare @T table(
Id int primary key,
Name nvarchar(255) not null,
ParentId int)

insert into @T values
(1, 'TestName1', NULL),
(2, 'TestName2', 1),
(3, 'TestName3', 2),
(4, 'TestName4', NULL),
(5, 'TestName5', 1)

declare @Id int = 1

;with cte as
(
select T.*
from @T as T
where T.Id = @Id
union all
select T.*
from @T as T
inner join cte as C
on T.ParentId = C.Id
)
select *
from cte

Result

Id          Name                 ParentId
----------- -------------------- -----------
1 TestName1 NULL
2 TestName2 1
5 TestName5 1
3 TestName3 2

Select Query with Recursion

You can use a Recursive CTE and then get the last level of "recursion" for each employee. Once you have that, you just check the manager_id of that last level to find out if it's transferred.

For example:

with
tablea as (
select 1 as uniqueId, 101 as employee_id, 102 as manager_id from dual union all
select 2 as uniqueId, 102 as employee_id, 103 as manager_id from dual union all
select 3 as uniqueId, 103 as employee_id, 104 as manager_id from dual union all
select 4 as uniqueId, 105 as employee_id, 106 as manager_id from dual union all
select 5 as uniqueId ,106 as employee_id, null from dual
),
tableb as (
select 101 as employee_id, 'first' as employee_name from dual union all
select 102 as employee_id, 'second' as employee_name from dual union all
select 103 as employee_id, 'third' as employee_name from dual union all
select 104 as employee_id, 'fourth' as employee_name from dual union all
select 105 as employee_id, 'fifth' as employee_name from dual union all
select 106 as employee_id, 'sixth' as employee_name from dual
),
n (employee_id, employee_name, lvl, manager_id) as (
select b.employee_id, b.employee_name, 1, a.manager_id
from tablea a
join tableb b on a.employee_id = b.employee_id
union all
select
n.employee_id, n.employee_name, lvl + 1, a.manager_id
from n
join tablea a on a.employee_id = n.manager_id
),
m (employee_id, max_lvl) as (
select employee_id, max(lvl) from n group by employee_id
)
select n.employee_id, n.employee_name,
case when n.manager_id is not null then 'True' else 'False' end as transferred
from n
join m on n.employee_id = m.employee_id and n.lvl = m.max_lvl
order by n.employee_id

Result:

EMPLOYEE_ID  EMPLOYEE_NAME  TRANSFERRED
----------- ------------- -----------
101 first True
102 second True
103 third True
105 fifth False
106 sixth False

CTE recursive select and insert in same table with new id's and some updated fields

One way to do this is in two steps. Insert the rows without the ids. Then figure out the ids:

insert into teams (name, activeyear, parentid)
select name, 2021, parentid
from teams
where actdiveyear = 2020;

Now update the values by looking up the corresponding ids. Window functions can help here:

insert into teams (name, activeyear, ParentTeamId)
select name, 2021, ParentTeamId
from teams
where activeyear = 2020;

with corresponding as (
select t2020.teamid as teamid_2020, t2021.teamid as teamid_2021
from teams t2020 join
teams t2021
on t2020.name = t2021.name
where t2020.activeyear = 2020 and t2021.activeyear = 2021
)
update t
set ParentTeamId = c.teamid_2021
from teams t join
corresponding c
on t.ParentTeamId = c.teamid_2020
where t.activeyear = 2021;

Here is a db<>fiddle.

Using SQL recursive query as AND statement

I think I've worked out what you want.

You need to recurse through all the nodes and their children, returning its state and its ultimate root parent_id.

Then aggregate by that ID and exclude any group that contains a row with state = 'not-legit'. In other words, flip the logic to a double negative.

WITH cte AS (
SELECT rH.id, rH.state, rH.id AS top_parent
FROM tbl_request as rH
WHERE (rH.state is null or rH.state <> 'not-legit')
AND rH.parent_id IS NULL
UNION ALL
SELECT rH.id, rH.state, cte.top_parent
FROM tbl_request as rH
JOIN cte
ON rH.parent_id = cte.id
)
SELECT top_parent
FROM cte
GROUP BY
cte.top_parent
HAVING COUNT(CASE WHEN cte.state = 'not-legit' THEN 1 END) = 0;

You could also change the logic back to a positive, but it would need to look like this:

HAVING COUNT(CASE WHEN cte.state is null or cte.state <> 'not-legit' THEN 1 END) = COUNT(*)

In other words, there are the same number of these filtered rows as there are all rows.

This feels more complex than what I have put above.

SQL Fiddle

How to SUM all subchildren tree in SQL recursively?

I've updated the fiddle to include the exact schema / data provided in the question, including the null issues. It also includes an example of my suggested changes.

The solution basically takes the given data and transforms it internally (in the CTE term nodes) so that the 2 top level category rows link to a common row, with id 0, so that the original logic I provided can be used to treat this as one hierarchical list of categories.

First, we find all the branch lists recursively. Each branch is identified by a corresponding root. Then, we aggregate the node quantities for each root/branch.

The fiddle

WITH RECURSIVE nodes AS (
SELECT id, COALESCE(idParentCategory, 0) AS idParentCategory
, sumSubtotal, sumQuantity
FROM tenant_category_transaction_view
UNION
SELECT 0, null, 0, 0
)
, cte AS (
SELECT t.*, t.id AS root
, idParentCategory AS idParentCategory0
, sumSubtotal AS sumSubtotal0
, sumQuantity AS sumQuantity0
FROM nodes AS t
UNION ALL
SELECT t.* , t0.root
, t0.idParentCategory0
, t0.sumSubtotal0
, t0.sumQuantity0
FROM cte AS t0
JOIN nodes AS t
ON t.idParentCategory = t0.id
)
SELECT root
, MIN(idParentCategory0) AS idParentCategory
, MIN(sumSubtotal0) AS sumSubtotal
, MIN(sumQuantity0) AS sumQuantity
, SUM(t1.sumSubtotal) AS total
FROM cte AS t1
GROUP BY root
ORDER BY root
;

The result:

















































rootidParentCategorysumSubtotalsumQuantitytotal
0null009890
109800989800
4020190
5430170
6540140

sql select parent child recursive in one field

You need to use a Recursive Common Table Expression.

There are many useful articles online.

Useful Links

Simple Talk: SQL Server CTE Basics

blog.sqlauthority: Recursive CTE

Here is a solution to your question:

   CREATE TABLE #TEST
(
id int not null,
idparent int not null,
jobno int not null
);

INSERT INTO #Test VALUES
(1,0,1),
(2,1,2),
(3,1,3),
(4,0,4),
(5,4,5),
(6,5,6);

WITH CTE AS (
-- This is end of the recursion: Select items with no parent
SELECT id, idparent, jobno, CONVERT(VARCHAR(MAX),jobno) AS ListJob
FROM #Test
WHERE idParent = 0
UNION ALL
-- This is the recursive part: It joins to CTE
SELECT t.id, t.idparent, t.jobno, c.ListJob + '/' + CONVERT(VARCHAR(MAX),t.jobno) AS ListJob
FROM #Test t
INNER JOIN CTE c ON t.idParent = c.id
)
SELECT * FROM CTE
ORDER BY id;

Recursive query with parent-child relation

Just another option using the data type hierarchyid

There are some additional features and functions associated with hierarchyid

Example

-- Optional See 1st WHERE
Declare @Top int = null --<< Sets top of Hier Try 2

;with cteP as (
Select ID
,parent_id
,Name
,HierID = convert(hierarchyid,concat('/',ID,'/'))
From YourTable
Where IsNull(@Top,-1) = case when @Top is null then isnull(parent_id ,-1) else ID end
--Where parent_id is null -- Use this where if you always want the full hierarchy
Union All
Select ID = r.ID
,parent_id = r.parent_id
,Name = r.Name
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.ID,'/'))
From YourTable r
Join cteP p on r.parent_id = p.ID)
Select Lvl = HierID.GetLevel()
,ID
,parent_id
,Name
From cteP A
Order By A.HierID

Results

Lvl ID  parent_id   Name
1 1 NULL P1
1 2 NULL P2
2 3 2 P2-1
2 4 2 P2-2
2 5 2 P2-3
3 6 5 P2-3-1
3 7 5 P2-3-2
1 8 NULL P3
2 9 8 P3-1

Just for fun, If I set @Top to 2, the results would be

Lvl ID  parent_id   Name
1 2 NULL P2
2 3 2 P2-1
2 4 2 P2-2
2 5 2 P2-3
3 6 5 P2-3-1
3 7 5 P2-3-2

How to select using WITH RECURSIVE clause

First of all, let us try to simplify and clarify algorithm description given on the manual page. To simplify it consider only union all in with recursive clause for now (and union later):

WITH RECURSIVE pseudo-entity-name(column-names) AS (
Initial-SELECT
UNION ALL
Recursive-SELECT using pseudo-entity-name
)
Outer-SELECT using pseudo-entity-name

To clarify it let us describe query execution process in pseudo code:

working-recordset = result of Initial-SELECT

append working-recordset to empty outer-recordset

while( working-recordset is not empty ) begin

new working-recordset = result of Recursive-SELECT
taking previous working-recordset as pseudo-entity-name

append working-recordset to outer-recordset

end

overall-result = result of Outer-SELECT
taking outer-recordset as pseudo-entity-name

Or even shorter - Database engine executes initial select, taking its result rows as working set. Then it repeatedly executes recursive select on the working set, each time replacing contents of the working set with query result obtained. This process ends when empty set is returned by recursive select. And all result rows given firstly by initial select and then by recursive select are gathered and feeded to outer select, which result becomes overall query result.

This query is calculating factorial of 3:

WITH RECURSIVE factorial(F,n) AS (
SELECT 1 F, 3 n
UNION ALL
SELECT F*n F, n-1 n from factorial where n>1
)
SELECT F from factorial where n=1

Initial select SELECT 1 F, 3 n gives us initial values: 3 for argument and 1 for function value.

Recursive select SELECT F*n F, n-1 n from factorial where n>1 states that every time we need to multiply last funcion value by last argument value and decrement argument value.

Database engine executes it like this:

First of all it executes initail select, which gives the initial state of working recordset:

F | n
--+--
1 | 3

Then it transforms working recordset with recursive query and obtain its second state:

F | n
--+--
3 | 2

Then third state:

F | n
--+--
6 | 1

In the third state there is no row which follows n>1 condition in recursive select, so forth working set is loop exits.

Outer recordset now holds all the rows, returned by initial and recursive select:

F | n
--+--
1 | 3
3 | 2
6 | 1

Outer select filters out all intermediate results from outer recordset, showing only final factorial value which becomes overall query result:

F 
--
6

And now let us consider table forest(id,parent_id,name):

id | parent_id | name
---+-----------+-----------------
1 | | item 1
2 | 1 | subitem 1.1
3 | 1 | subitem 1.2
4 | 1 | subitem 1.3
5 | 3 | subsubitem 1.2.1
6 | | item 2
7 | 6 | subitem 2.1
8 | | item 3

'Expanding full tree' here means sorting tree items in human-readable depth-first order while calculating their levels and (maybe) paths. Both tasks (of correct sorting and calculating level or path) are not solvable in one (or even any constant number of) SELECT without using WITH RECURSIVE clause (or Oracle CONNECT BY clause, which is not supported by PostgreSQL). But this recursive query does the job (well, almost does, see the note below):

WITH RECURSIVE fulltree(id,parent_id,level,name,path) AS (
SELECT id, parent_id, 1 as level, name, name||'' as path from forest where parent_id is null
UNION ALL
SELECT t.id, t.parent_id, ft.level+1 as level, t.name, ft.path||' / '||t.name as path
from forest t, fulltree ft where t.parent_id = ft.id
)
SELECT * from fulltree order by path

Database engine executes it like this:

Firstly, it executes initail select, which gives all highest level items (roots) from forest table:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
1 | | 1 | item 1 | item 1
8 | | 1 | item 3 | item 3
6 | | 1 | item 2 | item 2

Then, it executes recursive select, which gives all 2nd level items from forest table:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
2 | 1 | 2 | subitem 1.1 | item 1 / subitem 1.1
3 | 1 | 2 | subitem 1.2 | item 1 / subitem 1.2
4 | 1 | 2 | subitem 1.3 | item 1 / subitem 1.3
7 | 6 | 2 | subitem 2.1 | item 2 / subitem 2.1

Then, it executes recursive select again, retrieving 3d level items:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
5 | 3 | 3 | subsubitem 1.2.1 | item 1 / subitem 1.2 / subsubitem 1.2.1

And now it executes recursive select again, trying to retrieve 4th level items, but there are none of them, so the loop exits.

The outer SELECT sets the correct human-readable row order, sorting on path column:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
1 | | 1 | item 1 | item 1
2 | 1 | 2 | subitem 1.1 | item 1 / subitem 1.1
3 | 1 | 2 | subitem 1.2 | item 1 / subitem 1.2
5 | 3 | 3 | subsubitem 1.2.1 | item 1 / subitem 1.2 / subsubitem 1.2.1
4 | 1 | 2 | subitem 1.3 | item 1 / subitem 1.3
6 | | 1 | item 2 | item 2
7 | 6 | 2 | subitem 2.1 | item 2 / subitem 2.1
8 | | 1 | item 3 | item 3

NOTE: Resulting row order will remain correct only while there are no punctuation characters collation-preceeding / in the item names. If we rename Item 2 in Item 1 *, it will break row order, standing between Item 1 and its descendants.

More stable solution is using tab character (E'\t') as path separator in query (which can be substituted by more readable path separator later: in outer select, before displaing to human or etc). Tab separated paths will retain correct order until there are tabs or control characters in the item names - which easily can be checked and ruled out without loss of usability.

It is very simple to modify last query to expand any arbitrary subtree - you need only to substitute condition parent_id is null with perent_id=1 (for example). Note that this query variant will return all levels and paths relative to Item 1.

And now about typical mistakes. The most notable typical mistake specific to recursive queries is defining ill stop conditions in recursive select, which results in infinite looping.

For example, if we omit where n>1 condition in factorial sample above, execution of recursive select will never give an empty set (because we have no condition to filter out single row) and looping will continue infinitely.

That is the most probable reason why some of your queries hang (the other non-specific but still possible reason is very ineffective select, which executes in finite but very long time).

There are not much RECURSIVE-specific querying guidlines to mention, as far as I know. But I would like to suggest (rather obvious) step by step recursive query building procedure.

  • Separately build and debug your initial select.

  • Wrap it with scaffolding WITH RECURSIVE construct

    and begin building and debugging your recursive select.

The recommended scuffolding construct is like this:

WITH RECURSIVE rec( <Your column names> ) AS (
<Your ready and working initial SELECT>
UNION ALL
<Recursive SELECT that you are debugging now>
)
SELECT * from rec limit 1000

This simplest outer select will output the whole outer recordset, which, as we know, contains all output rows from initial select and every execution of recusrive select in a loop in their original output order - just like in samples above! The limit 1000 part will prevent hanging, replacing it with oversized output in which you will be able to see the missed stop point.

  • After debugging initial and recursive select build and debug your outer select.

And now the last thing to mention - the difference in using union instead of union all in with recursive clause. It introduces row uniqueness constraint which results in two extra lines in our execution pseudocode:

working-recordset = result of Initial-SELECT

discard duplicate rows from working-recordset /*union-specific*/

append working-recordset to empty outer-recordset

while( working-recordset is not empty ) begin

new working-recordset = result of Recursive-SELECT
taking previous working-recordset as pseudo-entity-name

discard duplicate rows and rows that have duplicates in outer-recordset
from working-recordset /*union-specific*/

append working-recordset to outer-recordset

end

overall-result = result of Outer-SELECT
taking outer-recordset as pseudo-entity-name


Related Topics



Leave a reply



Submit