I have an Oracle 12c database with a table with tree-like structure for organisational units (departments and so on):
CREATE TABLE "OUS" (
"ID" NUMBER(38,0) NOT NULL ENABLE,
"NAME" VARCHAR2(255 CHAR) NOT NULL ENABLE,
"PARENT_ID" NUMBER(38,0),
PRIMARY KEY("ID"),
CONSTRAINT "OUS_HIERARCHY_FK" FOREIGN KEY ("PARENT_ID") REFERENCES "OUS" ("ID") ON DELETE CASCADE
);
So, having structure like
| id | name | parent_id |
| -: | ------------- | --------: |
| 1 | Root | (NULL) |
| 2 | Territorial 1 | 1 |
| 3 | Regional 1-1 | 2 |
| 4 | Alpha dept | 3 |
| 5 | Beta dept | 3 |
| 6 | Regional 1-2 | 2 |
| 7 | Gamma dept | 6 |
| 8 | Delta dept | 7 |
| 9 | Territorial 2 | 1 |
| 10 | Regional 2-1 | 9 |
| 11 | Epsilon dept | 10 |
| 12 | Zeta dept | 10 |
You can create it with SQL like:
INSERT INTO ous (id, name, parent_id) VALUES ( 13, 'Root', NULL);
INSERT INTO ous (id, name, parent_id) VALUES ( 2, 'Territorial 1', 13);
INSERT INTO ous (id, name, parent_id) VALUES ( 1, 'Regional 1-1', 2);
INSERT INTO ous (id, name, parent_id) VALUES ( 5, 'Alpha dept', 1);
INSERT INTO ous (id, name, parent_id) VALUES ( 4, 'Beta dept', 1);
INSERT INTO ous (id, name, parent_id) VALUES ( 6, 'Regional 1-2', 2);
INSERT INTO ous (id, name, parent_id) VALUES ( 7, 'Gamma dept', 6);
INSERT INTO ous (id, name, parent_id) VALUES ( 8, 'Delta dept', 6);
INSERT INTO ous (id, name, parent_id) VALUES ( 9, 'Territorial 2', 13);
INSERT INTO ous (id, name, parent_id) VALUES ( 3, 'Regional 2-1', 9);
INSERT INTO ous (id, name, parent_id) VALUES ( 15, 'Epsilon dept', 3);
INSERT INTO ous (id, name, parent_id) VALUES ( 12, 'Zeta dept', 3);
I need to found some of OU, matching given criteria (like name = 'Alpha' OR name = 'Epsilon
) and get a subtree of these OU and their ancestors.
For example:
| id | name | parent_id |
| -: | ------------- | --------: |
| 1 | Root | (NULL) | ← Ancestor of Alpha and Epsilon
| 2 | Territorial 1 | 1 | ← Ancestor of Alpha
| 3 | Regional 1-1 | 2 | ← Ancestor of Alpha
| 4 | Alpha dept | 3 | ← Matches the WHERE clause!
| 9 | Territorial 2 | 1 | ← Ancestor of Epsilon
| 10 | Regional 2-1 | 9 | ← Ancestor of Epsilon
| 11 | Epsilon dept | 10 | ← Matches the WHERE clause!
I'm looked at various Hierarchical and recursive queries in SQL : Oracle Hierarchical queries and CTEs , but can't figure out a query that can return me such a result.
I'm using Oracle Database 12c.
I've tried queries like:
SELECT ous.* FROM ous
WHERE name = 'Alpha' OR name = 'Epsilon'
START WITH
parent_id IS NULL
CONNECT BY
PRIOR id = parent_id
ORDER SIBLINGS BY name;
But it returns 0 rows as WHERE is applied to all rows (so ancestors are being filtered)
Also I've tried:
WITH RECURSIVE all_nodes (id, parent_id, name) AS (
SELECT ous.id, ous.parent_id, name FROM ous WHERE (name = 'Alpha' OR name = 'Epsilon')
UNION
SELECT ous.id, ous.parent_id, name FROM ous INNER JOIN all_nodes ON ous.parent_id = all_nodes.id
)
SELECT * FROM all_nodes INNER JOIN ous ON all_nodes.id = ous.id ORDER BY name;
But it returns error SQL Error [905] [42000]: ORA-00905: keyword is missing
You can do this with a recursive CTE:
with t(name, id, parent_id) as (
select name, id, parent_id
from ous
where name in ('alpha', 'epsilon')
union all
select ous.name, ous.id, ous.parent_id
from t join
ous
on ous.id = t.parent_id
)
select distinct t.id, t.name, t.parent
from t
order by t.id;
The select distinct
is probably not necessary.
Recursive CTEs have the advantage of being standard SQL, so the logic is supported by many different databases.
Of course you can use a hierarchical query for this. The problem is that if you start with multiple leaves, at some point you will start getting duplicate rows. You can remove the duplicates with "distinct" but this will impair performance, especially on a very large table or if you start with too many leaves. At the end of the day, a recursive query is often more difficult to write, but more efficient than a hierarchical query.
For completeness, here is the hierarchical solution. Using the EMP table in the SCOTT schema for illustration. First showing the straight hierarchical query (and the duplicates in the output) and then the version with "distinct."
select empno, mgr
from scott.emp
start with empno in (7902, 7788)
connect by prior mgr = empno
;
EMPNO MGR
---------- ----------
7788 7566
7566 7839
7839
7902 7566
7566 7839
7839
select distinct empno, mgr
from scott.emp
start with empno in (7902, 7788)
connect by prior mgr = empno
;
EMPNO MGR
---------- ----------
7839
7566 7839
7902 7566
7788 7566
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.