Simple recursive query in Oracle - sql

I'm currently having some trouble understanding and writing recursive queries. I understand that recursive queries are used to search through hierarchies of information, but I haven't found a simple solution online that can travel up a hierarchy. For example, let's say that I have a relation that models a family tree:
create table family_tree (
child varchar(10)
parent varchar(10)
);
If I wanted to write a recursive query that travelled up this family tree, collecting all parents until origin, how should I go about this?
Thanks in advance.

You can use connect by clause.
In your case, SQL might look like:
select child, parent, level
from family_tree
connect by prior parent = child

If I wanted to write a recursive query that travelled up this family tree, collecting all parents until origin, how should I go about this?
Use a hierarchical query and the SYS_CONNECT_BY_PATH( column_name, delimiter ) function:
Oracle 18 Setup:
create table family_tree (
child varchar(10),
parent varchar(10)
);
INSERT INTO family_tree ( child, parent )
SELECT 'B', 'A' FROM DUAL UNION ALL
SELECT 'C', 'B' FROM DUAL UNION ALL
SELECT 'D', 'C' FROM DUAL UNION ALL
SELECT 'E', 'D' FROM DUAL UNION ALL
SELECT 'F', 'C' FROM DUAL;
Query 1:
SELECT SYS_CONNECT_BY_PATH( parent, ' -> ' ) || ' -> ' || child AS path
FROM family_tree
START WITH parent = 'A'
CONNECT BY PRIOR child = parent;
Results:
PATH
-------------------------
-> A -> B
-> A -> B -> C
-> A -> B -> C -> D
-> A -> B -> C -> D -> E
-> A -> B -> C -> F

There is an ANSI syntax that I'm not really familiar with and there is an Oracle syntax that I usually use. The Oracle syntax uses a CONNECT BY ... PRIOR clause to build the tree and a START WITH clause that tells the database where to start walking the tree. It will look like this:
SELECT child, parent, level
FROM family_tree
CONNECT BY ...
START WITH ...
The START WITH clause is easier. You're looking "up" the tree, so you'd pick a child where you want to start walking the tree. So this would look like START WITH parent = 'John'. This is our level 1 row. I'm assuming John's row will have him as the parent and no children, since it's the bottom of the tree.
Now, think about how rows in the tree relate to each other. If we're looking at a level 2 row, how do we know if it is the correct row to the "John" row? In this case, it will have John in the child column. So we want a clause of: CONNECT BY PRIOR parent = child. That means "the prior row's parent equals this row's child"
So the query looks like:
SELECT child, parent, level
FROM family_tree
CONNECT BY PRIOR parent = child
START WITH parent = 'John'
SQL Fiddle example
(This is a bit of a strange example since actual children have two parents, but that would make it more complicated.)

Are you familiar with the SCOTT.EMP table? It's in the "standard" SCOTT schema (which, unfortunately, is no longer pre-packaged with every copy of Oracle database, since version 12.1 or so). Check your database: you may find it there. Or ask your DBA about it.
Anyway: the table shows the 14 employees of a small business, and it includes the employee's ID as well as his or her manager's employee ID. So, suppose you start with a given employee and you want to find his or her highest-level boss. (Similar to your test problem.) In this particular hierarchy, the highest-level "ancestor" is unique, but that is irrelevant; the recursive query would work the same way if each department had a "head of department" and there was no CEO above the heads of department.
In this arrangement, it's easy to identify the "boss of all bosses" - he does not have a boss. In his row, the manager ID is null. This is a very common arrangement for the "root" (or "roots") of tree-like hierarchies.
Here is how you would find the boss, starting with a specific employee id, and using a recursive query - which is what I understand is what you are looking to practice on. (That is: if I understand correctly, you are not interested in solving the problem "by any means"; rather, you want to see how recursive queries work, in a small example so you can understand EVERYTHING that goes on.)
with
r ( empno, mgr ) as (
select empno, mgr -- ANCHOR leg of recursive query
from scott.emp
where empno = 7499
union all
select e.empno, e.mgr -- RECURSIVE leg of recursive query
from scott.emp e inner join r on e.empno = r.mgr
)
select empno
from r
where mgr is null
;
I will not try to guess where you may have difficulty understanding this example. Instead, I will wait for you to ask.

Related

Oracle Recursive Query Connect By Loop in data

I have a table that looks essentially like this (the first row pk1=1 is the parent row)
pk1
event_id
parent_event_id
1
123
123
2
456
123
3
789
456
Given any particular row in the above table, I need a query that returns all the related rows (up and down the hierarchy). I was trying to do this via an initial CTE table that grabs all the parent rows. Then use that as my base table and join back into the above table using a recursive query to navigate down (this seems wildly inefficient and I assume there is a better way???).
However, trying even the first step (populating my CTE table) and using a query like below to navigate up returns the connect by LOOP error.
select event_id, level
from myTable
start with pk1 = 2
connect by prior parent_event_id = event_id
I assume this is due to the fact the parent row is self-referencing (event_id = parent_event_id)? If I add in the NOCYCLE statement, then the recursion stops at the row prior to the actual parent.
Two questions:
1.) Is there a better way to do this in one query?
2.) Any clue how to tweak the above to get the parent row returned?
Thanks
I'm not super clear on what you mean by "all the related rows (up and down the tree)", but it might be possible.
Here, I'm adding more logic to the connect clause to go up OR down the tree. This includes direct parents and descendants, but also includes siblings/cousins to the starting node. That might or might not be what you want.
with mytable as (select 1 as pk1, 123 as event_id, 123 as parent_event_id from dual
union select 2, 456, 123 from dual
union select 3, 789, 456 from dual
union select 4, 837, 123 from dual)
select pk1, event_id, level, SYS_CONNECT_BY_PATH(event_id, '/') as path
from myTable
start with pk1 = 2
connect by nocycle (prior parent_event_id = event_id and prior event_id <> event_id)
or (prior event_id = parent_event_id)
The tweak to get the root parent to show up is just and prior event_id <> event_id - ie, don't go further up the tree if the parent node = the current node.
I added an example row (pk1=4) to show a sibling row (not direct parent or descendant) being returned.

Dynamic column in oracle sql

I want to query in database for some ledgername (like child),ledgergroupname (like parent,it's parent of ledgername) and all ascending parent's of ledgergroupname (or ledgername).Data i am searching from ACC_LEDGER table where ledgername and it's immediate parent termed as ledgergroupname are saved.Ascending parent of ledgergroupname are saved in ACC_LEDGERGROUP table.
sql--
select
pp.Ledgercode,
pp.Ledgername,
pp.Ledgergroupcode,
pp.Ledgergroupname,
(select Acc_Ledgergroup.Parentname from Acc_Ledgergroup where Acc_Ledgergroup.Ledgergrpcode=pp.Ledgergroupcode) as PARENTNAME
from
(select
LED.LEDGERCODE,
Led.Ledgername,
Led.Ledgergroupcode,
Led.Ledgergroupname
from ACC_LEDGER LED where Led.Ledgercode IN ('01003024007','01003024019'))pp
it gives me result--
what i want is --
so for every ledger i want to show parent upto root (it's level is different for different ledger means for some ledger it has 7 upper parent's to reach "root" parent).
I am trying to query something like this(it's for your understanding)--
select
pp.Ledgercode,
pp.Ledgername,
pp.Ledgergroupcode,
pp.Ledgergroupname,
(select Acc_Ledgergroup.Parentname from Acc_Ledgergroup where Acc_Ledgergroup.Ledgergrpcode=pp.Ledgergroupcode) as PARENTNAME
while(parentname != root)
{
select parent name from Ledger group
}
from the_table
So column selection from table or join of table is not fixed,it's depend on how much level of parent i have,how can i do that in oracle sql?
Generally this kind of problems can be solved with hierarchical queries (using CONNECT BY), however in Oracle a query cannot have a variable number of columns. As a workaround you can use sys_connect_by_path to concatenate all levels using some separator:
select sys_connect_by_path(Ledgergroupname, '/')
from Acc_Ledgergroup
where Ledgergroupname = 'root'
start with Ledgercode in ('01003024007', '01003024019')
connect by prior Ledgercode = Ledgergroupcode

Oracle - SQL with recursive WHERE clause

I have a table that stores the hierarchy of workgroups in our organization. It looks something like:
CREATE TABLE WORKGROUPS (
WORKGROUPID NUMBER NULL,
NAME VARCHAR2(100) NOT NULL,
PARENTWORKGROUPID NUMBER NOT NULL,
WORKGROUPLEVEL CHAR(1) DEFAULT 1 NOT NULL,
CONSTRAINT WORKGROUPS_PK PRIMARY KEY(WORKGROUPID)
)
For example, there might be three levels of workgroups deep:
ID 1 - Sales (PARENTWORKGROUPID = 0)
ID 2 - Business Sales (PARENTWORKGROUPID = 1)
ID 3 - West Coast B2B (PARENTWORKGROUPID = 2)
So 3's parent is 2, 2's parent is 1, and 1 is a top level workgroup with no parent, so we use 0.
Now we have a TASKS table. Each TASKS row has a WORKGROUPID column that points to a WORKGROUPID in the WORKGROUPS table.
I need to write a query that returns all TASKS that are under a given top-level workgroup, such as everything under Sales (which, in the example above, could be WORKGROUPIDs 1, 2 or 3. Basically, it's a recursive query.
I can think of some ways to do this using a LEFT JOIN for to check each level, but I'd rather stay away from solutions that hard-code in the number of levels since the database is designed to allow any number of tiers. Any other solution I can think of involves changing the table schema, which I can't do at the moment. Any ideas? Thanks!
Starting with Oracle 11gR2, the ANSI recursive WITH syntax is supported, and is an alternative to START WITH/CONNECT BY:
WITH wgs ( workgroupid, name ) AS
(
SELECT workgroupid, name FROM workgroups WHERE workgroupid = :top-lev-dept
UNION ALL
SELECT w.workgroupid, w.name FROM workgroups w, wgs WHERE parentworkgroupid = wgs.workgroupid
)
SELECT ...
The key here is:
WITH view (column_definition) AS
(
SELECT root rows
UNION ALL
SELECT sub rows FROM table JOIN view ON recursion condition
)
Oracle seems to have its own version of recursion (I use SQL Server and DB2, which use Common Table Expressions to power recursion), but I think this should possibly get what you want:
WITH WGS (
WORKGROUPID,
NAME
) AS (
SELECT WORKGROUPID,
NAME
FROM WORKGROUPS
START WITH WORKGROUPID = :top-lev-dept
CONNECT BY WORKGROUPID = PRIOR PARENTWORKGROUPID
)
SELECT DISTINCT T.*
FROM TASKS
INNER JOIN WGS W
ON (T.WORKGROUPID = W.WORKGROUPID)
You'd obviously change :top-lev-dept to the department you're searching for.
If that's not it, you might check out the Oracle reference page on Hierarchical Queries. That might get you started in the right direction...
You'll want to investigate the START WITH/CONNECT BY syntax.
Something like:
select * from workgroups
start with parentworkgroupid = 0
connect by prior workgroupid = parentworkgroupid;
Totally untested, but I think it will get you started.
Hope that helps.

Building a hierarchical tree with a single SQL query

I have a SQL table with the following structure.
id - int
par - int (relational to id)
name - varchar
Column par contains references to id or NULL if no reference, this table is meant to build an hierarchical tree.
Then, given the data:
id par name
1 NULL John
2 NULL Mario
3 1 George
4 3 Alfred
5 4 Nicole
6 2 Margaret
I want to retrieve a hierarchical tree, up to the last parent, from a given single id.
Example, I want to know the tree from Nicole to the last parent. So the query result will be:
id par name
5 4 Nicole
4 3 Alfred
3 1 George
1 NULL John
I would normally do this with a SQL query repeating over and over and building the tree server side but I do not want that now.
Is there any way to achieve this with a single SQL query?
I need this for either MySQL or PgSQL.
And I want to know also, if possible, is it also widely supported? In which versions of either MySQL or PgSQL can I expect support?
It is possible with a single query in Postgres using a recursive common table expression. This is not possible in MySQL as it is one of the few database to not support recursive CTEs.
It would look something like this (not tested)
WITH RECURSIVE tree (id, par, name) AS (
SELECT id, par, name
FROM the_table
WHERE name = 'Nicole'
UNION ALL
SELECT id, par, name
FROM the_table tt
JOIN tree tr ON tr.id = tt.par
)
SELECT *
FROM tree
For Postgres, see http://www.postgresql.org/docs/8.4/static/queries-with.html
MySQL doesn't support this syntax (unless it's in a beta/development tree somewhere). Oracle has something similar using connect by prior.
This article is probably what you need to look at:
http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/
In Oracle, this is done via:
SELECT [[LEVEL,]] id, par, name FROM my_table
START WITH name = 'Nicole'
CONNECT BY [[NOCYCLE]] id = PRIOR par
[[ORDER SIBLINGS BY name ASC]]
(my [[…]] syntax denotes optional query bits.
MySQL is planning to integrate such a feature. For PostgreSQL there is another answer helping you.

Using CONNECT BY to get all parents and one child in Hierarchy through SQL query in Oracle

I was going through some previous posts on CONNECT BY usage. What I need to find is that what to do if I want to get all the parents (i.e, up to root) and just one child for a node, say 4.
It seems Like I will have to use union of the following two:-
SELECT *
FROM hierarchy
START WITH id = 4
CONNECT BY id = PRIOR parent
union
SELECT *
FROM hierarchy
WHERE LEVEL =<2
START WITH
id = 4
CONNECT BY
parent = PRIOR id
Is there a better way to do this, some workaround that is more optimized?
You should be able to do this using a sub-select (and DISTINCT) to find all children of 4:
Select Distinct *
From hierarchy
Start With id In ( Select id
From hierarchy
Where parent = 4 )
Connect By id = Prior parent
Using UNION you could at least remove the CONNECT BY from your second query:
Select *
From hierarchy
Start With id = 4
Connect By id = Prior parent
Union
Select *
From hierarchy
Where parent = 4
Never use SELECT *, always name the columns you actually need. This makes your query easier to read, to maintain and to optimize.