Below is how data is structured in my table:
Notes:
P - indicates the parent
c - indicates the parent
A parent can have a parent ( having a child)
but a child can NOT have a child or either parent. Determine the layer/generations.
Column1,Column2
1-p,2-p:
2-p,3-p:
2-p,4-c:
3-p,5-c
how to read the above data:
1 has a child named 2;
2 has child 3 & 4;
3 has child 5.
So based on the above logic '1' has 3 layers.
How to write a query to determine no of layers in Oracle?
You can use a hierarchical query:
SELECT column1,
MAX( depth ) AS max_depth
FROM (
SELECT CONNECT_BY_ROOT( column1 ) AS column1,
LEVEL AS depth
FROM table_name
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY
PRIOR Column2 = Column1
)
GROUP BY
column1;
Which, for the sample data:
CREATE TABLE table_name ( Column1, Column2 ) AS
SELECT '1-p','2-p' FROM DUAL UNION ALL
SELECT '2-p','3-p' FROM DUAL UNION ALL
SELECT '2-p','4-c' FROM DUAL UNION ALL
SELECT '3-p','5-c' FROM DUAL;
Outputs:
COLUMN1 | MAX_DEPTH
:------ | --------:
1-p | 3
3-p | 1
2-p | 2
db<>fiddle here
Related
I have a oracle database where foreign key is enabled and I have tables A,B,C,D,E,F,AB,ABC,EF
Attaching the picture for hierarchy
'B' has parent 'D',
'AB' has parent 'A' as well as 'B',
'ABC' has parent 'AB' and 'C',
'EF' has parent 'E' & 'F',
Now {A,B,C,D,AB, ABC} has no link with {E,F,EF}. I have 2 requirements
I need to group them as group 1 & 2
I need to assign a hierarchy level for each group maintaining referential integrity as follows
How can I do it using sql/pl/sql in oracle database?
Assuming that you always end up at a single leaf node for each group then you can use a hierarchical query twice: from leaf-to-root to determine the groups; and then from root-to-leaf to determine the depths.
WITH groups ( child, parent, grp ) AS (
SELECT child,
parent,
DENSE_RANK() OVER ( ORDER BY CONNECT_BY_ROOT( child ) )
FROM table_name t
START WITH child NOT IN ( SELECT parent FROM table_name )
CONNECT BY PRIOR parent = child
),
depths ( child, parent, grp, depth ) AS (
SELECT child, parent, grp, LEVEL
FROM groups
START WITH parent NOT IN ( select child FROM table_name )
CONNECT BY PRIOR child = parent
)
SELECT table_name,
grp,
MAX( depth + depth_modifier ) AS depth
FROM depths
UNPIVOT ( table_name FOR depth_modifier IN ( child AS 1, parent AS 0 ) )
GROUP BY grp, table_name
ORDER BY grp, depth, table_name
Which, for your sample data:
CREATE TABLE table_name ( child, parent ) AS
SELECT 'B', 'D' FROM DUAL UNION ALL
SELECT 'AB', 'A' FROM DUAL UNION ALL
SELECT 'AB', 'B' FROM DUAL UNION ALL
SELECT 'ABC', 'AB' FROM DUAL UNION ALL
SELECT 'ABC', 'C' FROM DUAL UNION ALL
SELECT 'EF', 'E' FROM DUAL UNION ALL
SELECT 'EF', 'F' FROM DUAL;
Outputs:
TABLE_NAME | GRP | DEPTH
:--------- | --: | ----:
A | 1 | 1
C | 1 | 1
D | 1 | 1
B | 1 | 2
AB | 1 | 3
ABC | 1 | 4
E | 2 | 1
F | 2 | 1
EF | 2 | 2
If you don't always have a single leaf node for each group then you'll need a MUCH more complicated solution (probably written in PL/SQL).
db<>fiddle here
Hi I have a table as below and I'm trying to extract the data from them if and only if the below condition is satisfied.
ID Rank
45689 1
54789 2
98765 1
96541 2
98523 3
92147 4
96741 2
99999 10
If the ID starts with 4 and 9 or 5 and 9 and have same Rank then omit them. If ID starts with 9 and no matching Rank with other ID (starting with 4 or 5) then show them as result.
So My Output should look like
ID Rank
98523 3
92147 4
99999 10
How can I use case statement in where clause to filter the data?
If I understand correctly, you want to select only those ID that begin with a 9, and have a rank that is not also the rank of (another) ID that begins with 4 or 5. Is that correct?
The query below is for the case ID is of string data type (although it will work OK, probably, if ID is numeric data type - through implicit conversion).
select *
from your_table
where id like '9%'
and rank not in (
select rank
from your_table
where substr(id, 1, 1) in ('4', '5')
)
;
One option would be using COUNT() analytic function along with a conditional aggregation such as
WITH t2 AS
(
SELECT SUM(CASE WHEN SUBSTR(id,1,1) IN ('5','9') OR
SUBSTR(id,1,1) IN ('4','9') THEN 1 END ) OVER
(PARTITION BY Rank) AS count, t.*
FROM t -- your original table
)
SELECT id, rank
FROM t2
WHERE count = 1
Demo
You can use an analytic function to only query the table once:
SELECT id,
rank
FROM (
SELECT t.*,
COUNT( CASE WHEN id LIKE '4%' OR id LIKE '5%' THEN 1 END )
OVER ( PARTITION BY Rank )
AS num_match
FROM table_name t
WHERE id LIKE '4%'
OR id LIKE '5%'
OR id LIKE '9%'
)
WHERE id LIKE '9%'
AND num_match = 0;
Which, for the sample data:
CREATE TABLE table_name ( ID, Rank ) AS
SELECT 45689, 1 FROM DUAL UNION ALL
SELECT 54789, 2 FROM DUAL UNION ALL
SELECT 98765, 1 FROM DUAL UNION ALL
SELECT 96541, 2 FROM DUAL UNION ALL
SELECT 98523, 3 FROM DUAL UNION ALL
SELECT 92147, 4 FROM DUAL UNION ALL
SELECT 96741, 2 FROM DUAL UNION ALL
SELECT 99999, 10 FROM DUAL;
Outputs:
ID | RANK
----: | ---:
98523 | 3
92147 | 4
99999 | 10
db<>fiddle here
I have a dataset that has the following appearence.
A 1
A 2
A 3
B 1
B 2
B 3
B 4
Which is a result from using the following command
select
connect_by_root id as root,
level lvl
from
dbset
start with id in ('A','B')
connect by nocycle child = prior parent)
I want the result
A 3
B 4
That is, I want to extract the topmost root in each of the trees. Ideally I would like this to be done within the same command I have, but I am too much of a novice within the area to know how to.
Use ROW_NUMBER:
SELECT root, lvl
FROM
(
SELECT
connect_by_root id AS root,
level lvl,
row_number() over (PARTITION BY connect_by_root id ORDER BY level DESC) rn
FROM dbset
START WITH id IN ('A','B')
CONNECT BY NOCYCLE child = prior parent
) t
WHERE rn = 1
Just use group by :
create table t23 ( dt varchar(10) ,id1 int );
insert all
into t23 values('A',1)
into t23 values('A',2)
into t23 values('A',3)
into t23 values('B',1)
into t23 values('B',2)
into t23 values('B',3)
into t23 values('B',4)
select * from dual;
select * from t23;
select dt , max(id1)id1 from t23 group by dt;
The connect_by_isleaf function might be what you're after, e.g.:
WITH dbset AS (SELECT 'A' ID, 1 CHILD, NULL PARENT FROM dual UNION ALL
SELECT 'A' ID, 2 CHILD, 1 PARENT FROM dual UNION ALL
SELECT 'A' ID, 3 CHILD, 2 PARENT FROM dual UNION ALL
SELECT 'B' ID, 1 CHILD, NULL PARENT FROM dual UNION ALL
SELECT 'B' ID, 2 CHILD, 1 PARENT FROM dual UNION ALL
SELECT 'B' ID, 3 CHILD, 2 PARENT FROM dual UNION ALL
SELECT 'B' ID, 4 CHILD, 3 PARENT FROM dual)
SELECT connect_by_root ID AS root,
LEVEL lvl,
CONNECT_by_isleaf,
CHILD, PARENT
FROM dbset
WHERE CONNECT_by_isleaf = 1
START WITH ID IN ('A', 'B') AND PARENT IS NULL
CONNECT BY NOCYCLE PRIOR CHILD = PARENT
AND ID = PRIOR ID
ORDER BY root;
ROOT LVL CONNECT_BY_ISLEAF CHILD PARENT
---- ---------- ----------------- ---------- ----------
A 3 1 3 2
B 4 1 4 3
This is basically the same as Tim Biegeleisen's answer, just completed with test data, result, sql fiddle and a correction about the partitioning condition.
Note: I am happily delete this answer as soon as Tim's answer is corrected and completed.
Test data:
create table dbset (id varchar(1), child varchar(1), parent varchar(1) );
insert into dbset values('A','A','A');
insert into dbset values('X','A','X');
insert into dbset values('Z','X','Z');
insert into dbset values('B','6','6');
insert into dbset values('G','6','7');
insert into dbset values('H','7','8');
insert into dbset values('I','8','9');
Query:
SELECT root, lvl
FROM
(
SELECT
connect_by_root id AS root,
level lvl,
row_number() over (PARTITION BY connect_by_root id ORDER BY level DESC) rn
FROM dbset
START WITH id IN ('A','B')
CONNECT BY NOCYCLE child = prior parent
) t
WHERE rn = 1;
Result:
| ROOT | LVL |
|------|-----|
| A | 3 |
| B | 4 |
Sql fiddle: http://sqlfiddle.com/#!4/37c70/8
Use ranking function row_number() to get the top most record for each root.
select * from
(
select *,
row_number() over (partition by id order by level desc) Seq
from table
) t
where Seq = 1
I'm working on a query which use connect by prior.
I have written a query which retrieves all children of an entity. What I want is to retrieve both children and parents rows.
Here is my SQL :
Select *
From myTable tab
Connect By Prior tab.id= tab.child_id
Start With tab.id= 2;
How could I retrieve parents ?
Thanks.
Use UNION ALL to combine a query to get the children with another to get the ancestors.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE myTable ( id, child_id ) AS
SELECT 0, 1 FROM DUAL UNION ALL
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 3, 4 FROM DUAL UNION ALL
SELECT 4, 5 FROM DUAL UNION ALL
SELECT 3, 6 FROM DUAL UNION ALL
SELECT 0, 7 FROM DUAL UNION ALL
SELECT 1, 8 FROM DUAL;
Query 1:
SELECT * -- Child Rows
FROM mytable
START WITH id = 2
CONNECT BY PRIOR child_id = id
UNION ALL
SELECT * -- Ancestor Rows
FROM mytable
START WITH child_id = 2
CONNECT BY PRIOR id = child_id
Results:
| ID | CHILD_ID |
|----|----------|
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 3 | 6 |
| 1 | 2 |
| 0 | 1 |
An alternative approach to a hierarchical query, if you're on 11gR2 or higher, is recursive subquery factoring:
with rcte (id, child_id, some_other_col) as (
select id, child_id, some_other_col -- whichever columns you're interested in
from myTable
where id = 2
union all
select t.id, t.child_id, t.some_other_col -- whichever columns you're interested in
from rcte r
join myTable t
on t.id = r.child_id -- match parents
or t.child_id = r.id -- match children
)
cycle id set is_cycle to 1 default 0
select id, child_id, some_other_col -- whichever columns you're interested in
from rcte
where is_cycle = 0;
The anchor member finds your initial target row. The recursive member then looks for parents or children of each row found so far.
The final query can get whichever columns you want, do aggregation, etc.
(Possibly worth testing both approaches with real data to see if there is a performance difference, of course).
I have a parent-child relationship in an Oracle 9i database-table
like:
parent | child
1 | 2
2 | 3
2 | 4
null | 1
1 | 8
I need to get the absolute parent from a given child.
Say, I have child 4, it has to give me parent: 1
I already looked to CONNECT BY , but I can't find the solution.
you could use a CONNECT BY query to build the list of parents and then filter :
SQL> WITH tree AS (
2 SELECT 1 parent_id, 2 child_id FROM DUAL
3 UNION ALL SELECT 2 , 3 FROM DUAL
4 UNION ALL SELECT 2 , 4 FROM DUAL
5 UNION ALL SELECT null, 1 FROM DUAL
6 UNION ALL SELECT 1 , 8 FROM DUAL
7 )
8 SELECT child_id
9 FROM (SELECT *
10 FROM tree
11 CONNECT BY PRIOR parent_id = child_id
12 START WITH child_id = 4)
13 WHERE parent_id IS NULL;
CHILD_ID
----------
1
SELECT parent
FROM (
SELECT parent
FROM (
SELECT parent, level AS l
FROM mytable
START WITH
child = 4
CONNECT BY
child = PRIOR parent
)
ORDER BY
l DESC
)
WHERE rownum = 1
This will give you NULL as the absolute parent.
If you want 1, replace parent with child:
SELECT child
FROM (
SELECT child
FROM (
SELECT child, level AS l
FROM mytable
START WITH
child = 4
CONNECT BY
child = PRIOR parent
)
ORDER BY
l DESC
)
WHERE rownum = 1