Sort a tree data structure - sql

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

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

How to perform depth first search in postgreSQL

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!

Hierarchical SQL Queries: Best SQL query to obtain the whole branch of a tree from a [nodeid, parentid] pairs table given the end node id

Is there any way to send a recursive query in SQL?
Given the end node id, I need all the rows up to the root node (which has parentid = NULL) ordered by level. E.g. if I have something like:
nodeid | parentid
a | NULL
b | a
c | b
after querying for end_node_id = c, I'd get something like:
nodeid | parentid | depth
a | NULL | 0
b | a | 1
c | b | 2
(Instead of the depth I can also work with the distance to the given end node)
The only (and obvious) way I could come up with is doing a single query per row until I reach the parent node.
Is there a more efficient way of doing it?
If you are using mssql 2005+ you can do this:
Test data:
DECLARE #tbl TABLE(nodeId VARCHAR(10),parentid VARCHAR(10))
INSERT INTO #tbl
VALUES ('a',null),('b','a'),('c','b')
Query
;WITH CTE
AS
(
SELECT
tbl.nodeId,
tbl.parentid,
0 AS Depth
FROM
#tbl as tbl
WHERE
tbl.parentid IS NULL
UNION ALL
SELECT
tbl.nodeId,
tbl.parentid,
CTE.Depth+1 AS Depth
FROM
#tbl AS tbl
JOIN CTE
ON tbl.parentid=CTE.nodeId
)
SELECT
*
FROM
CTE
Ended up with the following solutions (where level is the distance to the end node)
Oracle, using hierarchical queries (thanks to the info provided by #Mureinik):
SELECT IDCATEGORY, IDPARENTCATEGORY, LEVEL
FROM TNODES
START WITH IDCATEGORY=122
CONNECT BY IDCATEGORY = PRIOR IDPARENTCATEGORY;
Example using a view so it boils down to a single standard SQL query (requires >= 10g):
CREATE OR REPLACE VIEW VNODES AS
SELECT CONNECT_BY_ROOT IDCATEGORY "IDBRANCH", IDCATEGORY, IDPARENTCATEGORY, LEVEL AS LVL
FROM TNODES
CONNECT BY IDCATEGORY = PRIOR IDPARENTCATEGORY;
SELECT * FROM VNODES WHERE IDBRANCH = 122 ORDER BY LVL ASC;
http://sqlfiddle.com/#!4/18ba80/3
Postgres >= 8.4, using a WITH RECURSIVE Common Table Expression query:
WITH RECURSIVE BRANCH(IDPARENTCATEGORY, IDCATEGORY, LEVEL) AS (
SELECT IDPARENTCATEGORY, IDCATEGORY, 1 AS LEVEL FROM TNODES WHERE IDCATEGORY = 122
UNION ALL
SELECT p.IDPARENTCATEGORY, p.IDCATEGORY, LEVEL+1
FROM BRANCH pr, TNODES p
WHERE p.IDCATEGORY = pr.IDPARENTCATEGORY
)
SELECT IDCATEGORY,IDPARENTCATEGORY, LEVEL
FROM BRANCH
ORDER BY LEVEL ASC
Example using a view so it boils down to a single standard SQL query:
CREATE OR REPLACE VIEW VNODES AS
WITH RECURSIVE BRANCH(IDBRANCH,IDPARENTCATEGORY,IDCATEGORY,LVL) AS (
SELECT IDCATEGORY AS IDBRANCH, IDPARENTCATEGORY, IDCATEGORY, 1 AS LVL FROM TNODES
UNION ALL
SELECT pr.IDBRANCH, p.IDPARENTCATEGORY, p.IDCATEGORY, LVL+1
FROM BRANCH pr, TNODES p
WHERE p.IDCATEGORY = pr.IDPARENTCATEGORY
)
SELECT IDBRANCH, IDCATEGORY, IDPARENTCATEGORY, LVL
FROM BRANCH;
SELECT * FROM VNODES WHERE IDBRANCH = 122 ORDER BY LVL ASC;
http://sqlfiddle.com/#!11/42870/2
For Oracle, as requested in the comments, you can use the connect by operator to produce the hierarchy, and the level pseudocolumn to get the depth:
SELECT nodeid, parentid, LEVEL
FROM t
START WITH parentid IS NULL
CONNECT BY parentid = PRIOR nodeid;

Removing duplicate subtrees from CONNECT-BY query in oracle

I have a heirarchical table in the format
CREATE TABLE tree_hierarchy (
id NUMBER (20)
,parent_id NUMBER (20)
);
INSERT INTO tree_hierarchy (id, parent_id) VALUES (2, 1);
INSERT INTO tree_hierarchy (id, parent_id) VALUES (4, 2);
INSERT INTO tree_hierarchy (id, parent_id) VALUES (9, 4);
When I run the Query:-
SELECT id,parent_id,
CONNECT_BY_ISLEAF leaf,
LEVEL,
SYS_CONNECT_BY_PATH(id, '/') Path,
SYS_CONNECT_BY_PATH(parent_id, '/') Parent_Path
FROM tree_hierarchy
WHERE CONNECT_BY_ISLEAF<>0
CONNECT BY PRIOR id = PARENT_id
ORDER SIBLINGS BY ID;
Result I am Getting is like this:-
"ID" "PARENT_ID" "LEAF" "LEVEL" "PATH" "PARENT_PATH"
9 4 1 3 "/2/4/9" "/1/2/4"
9 4 1 2 "/4/9" "/2/4"
9 4 1 1 "/9" "/4"
But I need an Oracle Sql Query That gets me only this
"ID" "PARENT_ID" "LEAF" "LEVEL" "PATH" "PARENT_PATH"
9 4 1 3 "/2/4/9" "/1/2/4"
This is a simpler example I have more that 1000 records in such fashion.When I run the above query,It is generating many duplicates.Can any one give me a generic query that will give complete path from leaf to root with out duplicates.Thanks for the help in advance
The root node in finite hierarchy must be always known.
According to the definition: http://en.wikipedia.org/wiki/Tree_structure
the root node is a node that has no parents.
To check if a given node is a root node, take "parent_id" and check in the table if exists a record with this id.
The query might look like this:
SELECT id,parent_id,
CONNECT_BY_ISLEAF leaf,
LEVEL,
SYS_CONNECT_BY_PATH(id, '/') Path,
SYS_CONNECT_BY_PATH(parent_id, '/') Parent_Path
FROM tree_hierarchy th
WHERE CONNECT_BY_ISLEAF<>0
CONNECT BY PRIOR id = PARENT_id
START WITH not exists (
select 1 from tree_hierarchy th1
where th1.id = th.parent_id
)
ORDER SIBLINGS BY ID;
You should point the id patently to build the path for. Now your query is building the path for all leaves which satisfy your condition. You need to use "start with" Let's try it like this:
SELECT id,parent_id,
CONNECT_BY_ISLEAF leaf,
LEVEL,
SYS_CONNECT_BY_PATH(id, '/') Path,
SYS_CONNECT_BY_PATH(parent_id, '/') Parent_Path
FROM tree_hierarchy
WHERE CONNECT_BY_ISLEAF<>0
CONNECT BY PRIOR id = PARENT_id
START WITH id = 2
ORDER SIBLINGS BY ID;

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