A view to get children when parent is given - sql

I have a table with data of parent and child.
Parent | Child
---------------
A B
B C
C D
I want to get all the children form this table when I put where condition for parent.
ex -
select * from view where parent = 'A'
result should be B,C,D.
How do I write a view to get this output.
Thanks in advance.

Here's one option:
SQL> with test (parent, child) as
2 (select 'A', 'B' from dual union all
3 select 'B', 'C' from dual union all
4 select 'C', 'D' from dual
5 )
6 select max(ltrim(sys_connect_by_path(child, '-'), '-')) result
7 from test
8 connect by prior child = parent
9 start with parent = 'A';
RESULT
---------------------------------------------------------------------------
B-C-D
SQL>

You can do it using Listagg and Hierarchy clause
SQLFiddle
SELECT LISTAGG(CHILD, ',') WITHIN GROUP (ORDER BY CHILD) FROM (
SELECT CHILD FROM TABLE1 START WITH PARENT = 'A' CONNECT BY PRIOR CHILD = PARENT);

Related

Returning result in Json and table type SQL Oracle

I have a table in which with repeating id.
The goal is (to make a select) to get all the results of the repeating id to pour against each id in Json format as id it's not in Json
id,yyyy,name
1,2010,a
1,2011,b
2,2010,a
3,2010,c
3,2011,a
4,2011,v
Desired result
id, column(json)
1 {"2010":"a","2011","b"}
2 {"2010":"a","2010":"c"}
3 {"2010":"c","2011":"a"}
4 {"2011":"v"}
with data(id, y, name) as (
select 1,2010,'a' from dual union all
select 1,2011,'b' from dual union all
select 2,2010,'a' from dual union all
select 3,2010,'c' from dual union all
select 3,2011,'a' from dual union all
select 4,2011,'v' from dual -- union all
)
select id, json_objectagg(
key to_char(y) value name) as obj
from data
group by id
;
1 {"2010":"a","2011":"b"}
2 {"2010":"a"}
3 {"2010":"c","2011":"a"}
4 {"2011":"v"}

Oracle Hierarchy to get all children and all parents of each id

I have a table with parent/child ids, and I'm trying to get a full list of all levels of parents AND children for a given id.
Basically, for a given id, go all the way down and all the way up the hierarchy.
I've tried connect by, but maybe a recursive CTE would be better?
select 'abc' as child, null as parent from dual union all
select 'mno' as child, 'abc' as parent from dual union all
select 'def' as child, 'abc' as parent from dual union all
select '123' as child, 'abc' as parent from dual union all
select 'qrs' as child, '123' as parent from dual union all
select '789' as child, 'def' as parent from dual union all
select 'xyz' as child, '123' as parent from dual
Ex:
Child
Parent
abc
null
mno
abc
def
abc
123
abc
qrs
123
789
def
xyz
123
For 123, the desired output:
xyz > 123 > abc
qrs > 123 > abc
For abc, the desired output:
xyz > 123 > abc
789 > def > abc
qrs > 123 > abc
mno > abc
Here's my attempt. It seems kind of hacky with the full_hier being a concatenation + substring of the child & parent paths. Plus, I'm getting additional results that I'm not sure how to filter out (Ex: def > abc is returned though I don't want it as it's captured in 789 > def > abc).
select
connect_by_root child,
substr(sys_connect_by_path(child, '>' ),2) as child_hier
, substr(sys_connect_by_path(parent, '>' ),2) as parent_hier
, case
when parent is null then substr(sys_connect_by_path(child, '>' ),2)
else substr(sys_connect_by_path(child, '>' ),2) || substr(substr(sys_connect_by_path(parent, '>' ),2), instr(substr(sys_connect_by_path(parent, '>' ),2),'>',1,1))
end as full_hier
, level
from
(
select 'abc' as child, null as parent from dual union all
select 'mno' as child, 'abc' as parent from dual union all
select 'def' as child, 'abc' as parent from dual union all
select '123' as child, 'abc' as parent from dual union all
select 'qrs' as child, '123' as parent from dual union all
select '789' as child, 'def' as parent from dual union all
select 'xyz' as child, '123' as parent from dual
) table_name
where 1=1
--and connect_by_isleaf = 1
--and connect_by_root child in ('123')
and child = 'abc'
connect by child = prior parent
--connect_by prior parent = child
Thanks for taking a look, I appreciate it!
Another method.
This time via recursive CTE's.
with cte_init (base) as (
select '123' as base
from dual
),
rcte_hierarchy_down (base, lvl, child, parent) as (
select
child as base
, 0 as lvl
, child
, parent
from test_hierarchy
where child in (select base from cte_init)
union all
select
cte.base
, cte.lvl-1
, t.child
, t.parent
from rcte_hierarchy_down cte
join test_hierarchy t
on t.child = cte.parent
),
rcte_hierarchy_up (lvl, child, parent, path) as (
select
1 as lvl
, child
, parent
, child||'>'||parent as path
from test_hierarchy h
where parent in (select child
from rcte_hierarchy_down
where parent is null)
union all
select
cte.lvl+1
, t.child
, t.parent
, t.child||'>'||cte.path
from rcte_hierarchy_up cte
join test_hierarchy t
on t.parent = cte.child
)
select distinct h.path
from rcte_hierarchy_up h
join cte_init i on h.path like '%'||i.base||'%'
and not exists (
select 1
from test_hierarchy t
where t.parent = h.child
)
PATH
qrs>123>abc
xyz>123>abc
Demo on db<>fiddle here
If I understand correctly, given any id (as an input - below I use a bind variable in the query), you need to find all its leaf descendants, and then for each such leaf, show the full path from leaf to the root of the hierarchy.
One way to do that is to traverse the hierarchy twice: first start from the given id and find all its leaf descendants, then traverse in the opposite direction to find all the "full paths".
While this may look (marginally) more elegant, it will be significantly less efficient. The better approach is along the lines you were trying already.
Below I use the with clause (and give column names in the subquery declaration - that is only supported since Oracle 11.2, if your version is 11.1 you will need to move the aliases into the select clauses as you were doing in your attempt).
with
table_name (child, parent) as (
select 'abc', null from dual union all
select 'mno', 'abc' from dual union all
select 'def', 'abc' from dual union all
select '123', 'abc' from dual union all
select 'qrs', '123' from dual union all
select '789', 'def' from dual union all
select 'xyz', '123' from dual
)
, a (ancestor_path) as (
select sys_connect_by_path(child, '>')
from table_name
where connect_by_isleaf = 1
start with child = :i_child
connect by child = prior parent
)
, d (descendant_path) as (
select substr(sys_connect_by_path(child, '>'), 2)
from table_name
where connect_by_isleaf = 1
start with parent = :i_child
connect by parent = prior child
)
select d.descendant_path || a.ancestor_path as full_path
from d cross join a
;
Here's a method that first descends down the tree to get the root parent(s) from the chosen one.
Then uses those roots to climb back up.
The resulting paths are then filtered on the chosen child.
And what remains are those where the final child isn't a parent.
create table test_hierarchy (
child varchar(3),
parent varchar(3),
primary key (child)
);
insert into test_hierarchy (child, parent)
select 'abc' as child, null as parent from dual union all
select 'mno' as child, 'abc' as parent from dual union all
select 'def' as child, 'abc' as parent from dual union all
select '123' as child, 'abc' as parent from dual union all
select 'qrs' as child, '123' as parent from dual union all
select '789' as child, 'def' as parent from dual union all
select 'xyz' as child, '123' as parent from dual;
with cte (base) as (
select '123' from dual
)
select *
from
(
select
sys_connect_by_path(child,'>') as path
, cte.base
, level
, connect_by_root child as child
from test_hierarchy
cross join cte
where child in (
select connect_by_root child
from test_hierarchy
where child in (select base from cte)
and connect_by_root parent is null
connect by prior child = parent
)
connect by prior parent = child
) q
where path like '%'||base||'%'
and not exists (
select 1
from test_hierarchy t
where t.parent = q.child
)
PATH
BASE
LEVEL
CHILD
>qrs>123>abc
123
3
qrs
>xyz>123>abc
123
3
xyz
Demo on db<>fiddle here

oracle hierarchy that gets wrong reference parent

I have the below data example for some items, those items have reference and reference parents. My issue is some references have wrong parents,which lead to infinite loop
let me explain, item with reference K001 doesn't have reference because it is the main product, reference k002 its parent is k001. k003 its parents is k001, data here is correct, because k001 is the parent with reference First.
For type B data is incorrect, m992 its parent is m993, however reference m993 its parent is m992, this is incorrect, the reference should point to a reference_parent First so to fix this the reference_parent for reference m992 should be m991 not m993.
For type C data also is incorrect, reference K883 has wrong reference_Parent because it hask886.
Because when I go to reference K886 I check its reference parent which is k885 then i check k885 reference parent which is k884 , then i check reference parent for k884 which is k883, then when I check k883 reference parent it gives me k886, here its wrong and I go an infinite loop, because k886 reference parent is kk85. To correct the reference parent of item k883 should be k882
Type Reference Reference_Parent
A K001 First
A K002 K001
A K003 K001
B M991 First
B M992 M993 --wrong parent data
B M993 M992
C K881 First
C K882 K881
C K883 K886 --wrong parent data
C K884 K883
C K885 K884
C K886 K885
A L001 First
A L002 L001
A L003 L002
A L004 L002
A L005 L002
A L006 L004
I tried with below query but its not giving me my required result and I am not expert with heirachy queries
SELECT reference,reference_parent,LEVEL ,SYS_CONNECT_BY_PATH(reference, '/') "Path"
FROM ITEMS
WHERE TYPE='B'
START WITH reference_parent = 'First'
CONNECT BY nocycle PRIOR reference_parent = 'First'
My required result is to give me
B M992 M993 --wrong parent data
C K883 K886 --wrong parent data
Oracle Setup:
CREATE TABLE ITEMS ( Type, Reference, Reference_Parent ) AS
SELECT 'A', 'K001', 'First' FROM DUAL UNION ALL
SELECT 'A', 'K002', 'K001' FROM DUAL UNION ALL
SELECT 'A', 'K003', 'K001' FROM DUAL UNION ALL
SELECT 'B', 'M991', 'First' FROM DUAL UNION ALL
SELECT 'B', 'M992', 'M993' FROM DUAL UNION ALL --wrong parent data
SELECT 'B', 'M993', 'M992' FROM DUAL UNION ALL
SELECT 'C', 'K881', 'First' FROM DUAL UNION ALL
SELECT 'C', 'K882', 'K881' FROM DUAL UNION ALL
SELECT 'C', 'K883', 'K886' FROM DUAL UNION ALL --wrong parent data
SELECT 'C', 'K884', 'K883' FROM DUAL UNION ALL
SELECT 'C', 'K885', 'K884' FROM DUAL UNION ALL
SELECT 'C', 'K886', 'K885' FROM DUAL UNION ALL
SELECT 'A', 'L001', 'First' FROM DUAL UNION ALL
SELECT 'A', 'L002', 'L001' FROM DUAL UNION ALL
SELECT 'A', 'L003', 'L002' FROM DUAL UNION ALL
SELECT 'A', 'L004', 'L002' FROM DUAL UNION ALL
SELECT 'A', 'L005', 'L002' FROM DUAL UNION ALL
SELECT 'A', 'L006', 'L004' FROM DUAL;
Query:
SELECT DISTINCT
MIN( CONNECT_BY_ROOT( Reference_Parent ) )
KEEP ( DENSE_RANK FIRST ORDER BY CONNECT_BY_ROOT( Reference ) )
AS reference_parent,
MIN( CONNECT_BY_ROOT( Reference ) )
AS reference
FROM ITEMS
START WITH Reference NOT IN (
SELECT reference
FROM ITEMS
START WITH Reference_Parent = 'First'
CONNECT BY PRIOR Reference = Reference_Parent
)
CONNECT BY NOCYCLE PRIOR Reference = Reference_Parent
GROUP BY Reference
Output:
REFERENCE_PARENT | REFERENCE
:--------------- | :--------
K886 | K883
M993 | M992
db<>fiddle here
Explanation:
You can find all the rows in the hierarchy starting with 'First' using the query:
SELECT reference
FROM ITEMS
START WITH Reference_Parent = 'First'
CONNECT BY PRIOR Reference = Reference_Parent
Your rows will be disconnected from these so you want a hierarchical query like:
SELECT *
FROM ITEMS
START WITH Reference NOT IN (
SELECT reference
FROM ITEMS
START WITH Reference_Parent = 'First'
CONNECT BY PRIOR Reference = Reference_Parent
)
CONNECT BY NOCYCLE PRIOR Reference = Reference_Parent
This just gives you the disconnected components and will generate lots of duplicate rows as it will start the hierarchy at every point in the cycle so some additional work is required to find the lowest Reference in the cycle (which appears to be where you are saying the wrong parents have occurred). This can be found by getting the MINimum values for CONNECT_BY_ROOT( Reference ) for each Reference; which gives you the query above.
Query 2:
You could also just look for cycles without eliminating the rows connected to 'First':
SELECT DISTINCT
MIN( Type ) KEEP ( DENSE_RANK FIRST ORDER BY Reference )
AS Type,
MIN( Reference ) AS Reference,
MIN( Reference_Parent ) KEEP ( DENSE_RANK FIRST ORDER BY Reference )
AS Reference_Parent
FROM (
SELECT Type,
Reference,
Reference_parent,
CONNECT_BY_ROOT( Reference_Parent ) AS root_reference_parent,
CONNECT_BY_ROOT( Reference ) AS root_reference,
CONNECT_BY_ISCYCLE AS isCycle
FROM ITEMS
CONNECT BY NOCYCLE PRIOR Reference = Reference_Parent
)
GROUP BY
root_Reference,
root_Reference_Parent
HAVING MAX( isCycle ) = 1
db<>fiddle here

Find the top-level parent group each record belongs to

I have hierarchical data with the following structure. The levels are 1 to 6, so instead of doing 6 joins and then coalesce, how can I find what is the top-level parent (which has no parent_nr).
I've tried the accepted answer here,
SELECT
aa.code_nr,
aa.parent_nr,
CONNECT_BY_ROOT aa.code_nr AS "Top Level ID"
FROM mytable_t aa
CONNECT BY PRIOR aa.code_nr = aa.parent_nr
;
but it only gives me the next level as "Top Level ID" and not the final level (A)
Oracle Setup:
CREATE TABLE my_table ( code_nr, parent_nr ) AS (
SELECT 'A', NULL FROM DUAL UNION ALL
SELECT 'A.1', 'A' FROM DUAL UNION ALL
SELECT 'A.1.1', 'A.1' FROM DUAL UNION ALL
SELECT 'A.1.1.1', 'A.1.1' FROM DUAL UNION ALL
SELECT 'A.1.1.2', 'A.1.1' FROM DUAL UNION ALL
SELECT 'A.1.1.1.1', 'A.1.1.1' FROM DUAL UNION ALL
SELECT 'A.1.1.2.1', 'A.1.1.2' FROM DUAL UNION ALL
SELECT 'A.1.1.2.2', 'A.1.1.2' FROM DUAL;
Query:
SELECT LEVEL,
code_nr AS root_code_nr,
CONNECT_BY_ROOT( code_nr ) AS code_nr
FROM my_table
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY PRIOR parent_nr = code_nr;
Output:
LEVEL ROOT_CODE CODE_NR
----- --------- ---------
1 A A
2 A A.1
3 A A.1.1
4 A A.1.1.1
5 A A.1.1.1.1
4 A A.1.1.2
5 A A.1.1.2.1
5 A A.1.1.2.2
You could try the following:
SELECT
aa.code_nr,
aa.parent_nr,
substr(SYS_CONNECT_BY_PATH(aa.code_nr, '/'),2,instr(SYS_CONNECT_BY_PATH(aa.code_nr, '/'),'/'))
FROM mytable_t aa
CONNECT BY PRIOR aa.code_nr = aa.parent_nr
;

Oracle pivot a table based on column value using like

sample table
Child Parent
FF00001 12345
AA00002 12345
GG00003 12345
TT00003 12345
What I want is a table like this
Parent FF AA GG TT
12345 FF00001 AA00002 GG00003 TT00003
The numbers after the first 2 letters can be anything but I know that they are always AA, FF, GG, TT etc. Can I pivot on a like statement? like 'AA%'
You can use aggregation like this:
select
parent,
max(case when child like 'FF%' then child end) FF,
max(case when child like 'AA%' then child end) AA,
max(case when child like 'GG%' then child end) GG,
max(case when child like 'TT%' then child end) TT
from your_table
group by parent;
Other way is to find the prefix in a subquery using substr and then apply pivot on it.
select *
from (
select
child,
parent,
substr(child, 1, 2) prefix
from your_table
)
pivot (
max(child) for prefix in ('FF' as FF,'AA' as AA,'GG' as GG,'TT' as TT)
)
Both produces:
PARENT FF AA GG TT
---------------------------------------
12345 FF00001 AA00002 GG00003 TT00003
If you have multiple values with same prefix and you want to keep them all, use row_number() window function in the subquery and then apply pivot:
with your_table (Child , Parent) as (
select 'FF00001', 12345 from dual union all
select 'FF00002', 12345 from dual union all
select 'AA00002', 12345 from dual union all
select 'GG00003', 12345 from dual union all
select 'TT00003', 12345 from dual
)
-- test data setup ends. See the solution below --
select parent, FF, AA, GG, TT
from (
select
child,
parent,
substr(child, 1, 2) prefix,
row_number() over (partition by parent, substr(child, 1, 2) order by child)
from your_table
)
pivot (
max(child) for prefix in ('FF' as FF,'AA' as AA,'GG' as GG,'TT' as TT)
)
Produces:
PARENT FF AA GG TT
---------------------------------------
12345 FF00001 AA00002 GG00003 TT00003
12345 FF00002 - - -