How to perform depth first search in postgreSQL - sql

I have simple structure:
CREATE TABLE room_chart(
room_id INT,
parent_id INT,
);
I can perform breadth-first search with this code
WITH RECURSIVE r AS (
SELECT room_id , parent_id FROM room_chart WHERE parent_id is null
UNION
SELECT room_chart.room_id, room_chart.parent_id FROM room_chart, r WHERE room_chart.parent_id = r.room_id
)
SELECT * from room_list where room_id in (SELECT room_id from r);
But how can I perform depth-first search to find all the routes in this tree for example? thanks in advance!

Related

postgresql recursive category tree where are the children in the top parent's array

I reviewed many solutions and did not find a suitable example.
I have a certain selection
CREATE TABLE category
(
uuid uuid not null primary key,
name character varying(32),
parent_uuid uuid references category(uuid),
);
INSERT INTO category
(uuid, name, parent_uuid)
VALUES
('8a70180b-3644-4b17-af5f-93cbe0090cce', 'Electronics', null),
('d9093660-241a-48f6-bf09-b6a8c6c7f12a', 'Microcontrollers', '8a70180b-3644-4b17-af5f-93cbe0090cce'),
('376ae1cb-425d-44d2-b19a-19b6f1e86314', 'Arduino, 'd9093660-241a-48f6-bf09-b6a8c6c7f12a'),
('5d5f174a-5c8e-4d12-912f-8173e255e35a', 'ESP8266', 'd9093660-241a-48f6-bf09-b6a8c6c7f12a'),
('f79f5fa0-6eaf-465b-9b14-e3b49c5ac9ef', 'Food', null),
('8aa95eda-7963-40ef-be44-076cdf06c5c1', 'Meat', 'f79f5fa0-6eaf-465b-9b14-e3b49c5ac9ef');
Ultimately I want to build a tree
Electronics
Microcontrollers
Arduino
ESP8266
Food
Meat
I managed to make a selection of parents
WITH RECURSIVE nodes(uuid, name, parents) AS (
SELECT
uuid,
name,
ARRAY[]::uuid[]
FROM category
WHERE parent_uuid IS null
UNION ALL
SELECT
category.uuid,
category.name,
parents || nodes.uuid
FROM category
JOIN nodes ON nodes.uuid = category.parent_uuid
)
SELECT * FROM nodes;
result:
uuid
name
parents
8a70180b-3644-4b17-af5f-93cbe0090cce
Electronics
{}
f79f5fa0-6eaf-465b-9b14-e3b49c5ac9ef
Food
{}
8aa95eda-7963-40ef-be44-076cdf06c5c1
Meat
{f79f5fa0-6eaf-465b-9b14-e3b49c5ac9ef}
d9093660-241a-48f6-bf09-b6a8c6c7f12a
Microcontrollers
{8a70180b-3644-4b17-af5f-93cbe0090cce}
376ae1cb-425d-44d2-b19a-19b6f1e86314
Arduino
{8a70180b-3644-4b17-af5f-93cbe0090cce, d9093660-241a-48f6-bf09-b6a8c6c7f12a}
5d5f174a-5c8e-4d12-912f-8173e255e35a
ESP8266
{8a70180b-3644-4b17-af5f-93cbe0090cce, d9093660-241a-48f6-bf09-b6a8c6c7f12a}
However, this is not ideal for me. I would prefer this
uuid
name
children
8a70180b-3644-4b17-af5f-93cbe0090cce
Electronics
[{d9093660-241a-48f6-bf09-b6a8c6c7f12a, 376ae1cb-425d-44d2-b19a-19b6f1e86314}, {d9093660-241a-48f6-bf09-b6a8c6c7f12a, 5d5f174a-5c8e-4d12-912f-8173e255e35a}]
f79f5fa0-6eaf-465b-9b14-e3b49c5ac9ef
Food
[{8aa95eda-7963-40ef-be44-076cdf06c5c1}]
I don't quite understand how to do it. Could you help me please?
Keeping the base/root uuid & name in the recursive loop can help for the aggregation.
The solution below uses an EXISTS to know when the recursion ended.
WITH RECURSIVE RCTE_NODES AS (
SELECT
uuid
, name
, uuid as root_uuid
, name as root_name
, 1 as lvl
, ARRAY[]::uuid[] as children
, true as has_next
FROM category
WHERE parent_uuid IS null
UNION ALL
SELECT
cat.uuid
, cat.name
, cte.root_uuid
, cte.root_name
, cte.lvl+1
, cte.children || cat.uuid
, (exists(select 1 from category cat2 where cat2.parent_uuid = cat.uuid))
FROM RCTE_NODES cte
JOIN category cat
ON cat.parent_uuid = cte.uuid
)
SELECT root_uuid as uuid, root_name as name
, array_agg(children) as children
FROM RCTE_NODES
WHERE has_next = false
GROUP BY root_uuid, root_name
ORDER BY root_uuid;
uuid
name
children
8a70180b-3644-4b17-af5f-93cbe0090cce
Electronics
{{d9093660-241a-48f6-bf09-b6a8c6c7f12a,376ae1cb-425d-44d2-b19a-19b6f1e86314},{d9093660-241a-48f6-bf09-b6a8c6c7f12a,5d5f174a-5c8e-4d12-912f-8173e255e35a}}
f79f5fa0-6eaf-465b-9b14-e3b49c5ac9ef
Food
{{8aa95eda-7963-40ef-be44-076cdf06c5c1}}
Test on db<>fiddle here

Sort a tree data structure

I have a table like this:
CREATE TABLE tree (
id integer NOT NULL,
name character varying(50) NOT NULL,
parentid integer,
displayorder integer NOT NULL,
CONSTRAINT tree_id PRIMARY KEY (id)
)
The displayorders are relative in the same parent.
I am stuck on sorting this data to have the output like this:
1 -> 1.1 -> 1.1.1 -> 1.1.2 -> 1.1.3 -> 1.2 -> 1.3 -> 2 -> 3
Highly appreciated if you could help me out. Thanks!
You need a recursive query to walk the tree. To properly apply the displayorder on each level, you need to also collect the path to each node, to make sorting possible:
with recursive all_nodes as (
select id, name, parentid, displayorder, array[id] as path
from tree
where parentid is null
union all
select c.id, c.name, c.parentid, c.displayorder, p.path||c.id
from tree c
join all_nodes p on c.parentid = p.id
)
select id, name
from all_nodes
order by path, displayorder;
Online example: http://rextester.com/MJEL66144

Oracle CONNECT BY recursive child to parent query, include ultimate parent that self references

In the following example
id parent_id
A A
B A
C B
select id, parent_id
from table
start with id = 'A'
connect by nocycle parent_id = prior id
I get
A A
B A
C B
In my database I have millions of rows in the table and deep and wide hierarchies and I'm not interested in all children. I can derive the children I'm interested in. So I want to turn the query on its head and supply START WITH with the children ids. I then want to output the parent recursively until I get to the top. In my case the top is where the id and parent_id are equal. This is what I'm trying, but I can't get it to show the top level parent.
select id, parent_id
from table
START WITH id = 'C'
CONNECT BY nocycle id = PRIOR parent_id
This gives me
C B
B A
It's not outputting the A A. Is it possible to do this? What I'm hoping to do is not show the parent_id as a separate column in the output, but just show the name relating to the id. The hierarchy is then implied by the order.
I got that result by using WITH clause.
WITH REC_TABLE ( ID, PARENT_ID)
AS
(
--Start WITH
SELECT ID, PARENT_ID
FROM table
WHERE ID='C'
UNION ALL
--Recursive Block
SELECT T.ID, T.PARENT_ID
FROM table T
JOIN REC_TABLE R
ON R.PARENT_ID=T.ID
AND R.PARENT_ID!=R.ID --NoCycle rule
)
SELECT *
FROM REC_TABLE;
And it seems to work that way too.
select id, parent_id
from T
START WITH id = 'C'
CONNECT BY id = PRIOR parent_id and parent_id!= prior id;
-- ^^^^^^^^^^^^^^^^^^^^
-- break cycles
Hope it helps.

sql function for checking if two persons have common ancestors

I have to solve the following problem: create a sql function that check if two persons have common ancestors, but I'm stuck.
I create
create type person as object
(
first_name varchar2(10),
last_namevarchar2(10)
);
and Persons table
create table client_iulia
(Person_Id varchar2(13)constraint pk_id_client primary key,
Mother_Id varchar2(13),
Father_Id varchar2(13),
Name person);
What I am trying to do is a function with three parameters (two people and search level) to return 1 if there are common ancestors and 0 otherwise)
Please, if anyone has any idea, help me.
Sorry for my poor english.
Recursive CTE is the way to go. I think it is the easiest to understand. There may be faster ways, but this should be clear how it works. I don't have access to an oracle server so I might have typos, I show two steps below so you can test and understand how it works.
1) Find all ancestors for a single input
with ancestors as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPerson
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestors a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
)
SELECT *
FROM ancestors
2) Find all ancestors for two targets
with ancestorsA as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPersonA
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestorsA a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
), ancestorsB as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPersonB
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestorsB a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
)
SELECT A.*
FROM ancestorsA A
JOIN ancestorsB B ON A.Person_Id = B.Person_Id
Wow, that's tricky. You need recursive queries to get all ancestors of either persons. As there are two parents, you will get pairs of mother and father, however, which you need to split into single ancestors. Then look if the sets have at least one ancestor in common. This statement gives you 1 when there is at least one common ancestor, else 0:
with ancestors_of_person_1 as
(
select mother_id, father_id
from client_iulia
start with person_id = 1
connect by person_id = prior mother_id or person_id = prior father_id
)
, ancestors_of_person_2 as
(
select mother_id, father_id
from client_iulia
start with person_id = 2
connect by person_id = prior mother_id or person_id = prior father_id
)
select count(*)
from
(
(
select mother_id as ancestor from ancestors_of_person_1
union
select father_id as ancestor from ancestors_of_person_1
)
intersect
(
select mother_id as ancestor from ancestors_of_person_2
union
select father_id as ancestor from ancestors_of_person_2
)
)
where rownum = 1;
EDIT: Here is an sqlfiddle. Person 1 and 3 have ancestor 122 in common, whereas person 1 and 2 have none in common. Try it: sqlfiddle

Is there a better way to get the last level of groups from a table?

I have a Group table and within that table, there is a ParentId column that denotes a groups parent within the Group table. The purpose is to build a dynamic menu from these groups. I know I can loop and grab the last child and construct a result set, but I'm curious if there's a more SQL-y way of accomplishing this.
The table has Id, ParentId and Title fields of int, int and varchar.
Basically, a hierarchy may be constructed this way (People is the base group):
People -> Male -> Boy
-> Man
-> Female
I want to grab the last child(ren) of each branch. So, {Boy, Man, Female} in this case.
As I mentioned, getting that info isn't a problem. I'm just looking for a better way of getting it without having to write a bunch of unions and loops where I can basically change the base group and traverse the entire hierarchy outward, dynamically. I'm not really a Db guy, so I don't know if there's a slick way of doing this or not.
To get the leaf levels for one of many hierarchies, you can use a Recursive Common Table Expressions (CTEs) to enumerate the hierarchy, and then check which members are not the parent of another group to filter to the leaves:
Declare #RootID int = 1
;with cte as (
select
Id,
ParentId,
Title
From
Groups
Where
Id = #RootID
Union All
Select
g.Id,
g.ParentId,
g.Title
From
cte c
Inner Join
Groups g
On c.Id = g.ParentID
)
Select
*
From
cte g
Where
Not Exists (
Select
'x'
From
Groups g2
Where
g2.ParentID = g.Id
);
You can also do this with a left join rather than a not exists
http://sqlfiddle.com/#!6/8f1aa/9
Since you are using SQL Server 2012 you could take advantage of hierarchyid; here is an example following Laurence's schema:
CREATE TABLE Groups
(
Id INT NOT NULL
PRIMARY KEY
, Title VARCHAR(20)
, HID HIERARCHYID
)
INSERT INTO Groups
VALUES ( 1, 'People', '/' ),
( 2, 'Male', '/1/' ),
( 3, 'Female', '/2/' ),
( 4, 'Boy', '/1/1/' ),
( 5, 'Man', '/1/2/' );
SELECT Id
, Title
FROM Groups
WHERE HID NOT IN ( SELECT HID.GetAncestor(1)
FROM Groups
WHERE HID.GetAncestor(1) IS NOT NULL )
http://sqlfiddle.com/#!6/00330/1/0
Results:
ID TITLE
3 Female
4 Boy
5 Man