4

Account table:

      ID_ACC    PARENT_ACC
        76543    76543      <-edit
        18252    76543
        34456    76543
        456567   76543
        457564   65424
        45654242 6556756

Info table:

      ID     account_id  product_id
    875621     18252       98567
    875621     34456       98567

Product table:

    ACCOUNTID  PRODUCT_ID 
      456452     98567

Account table contain ID of accounts. But some of account(ID_ACC) has parent account (Parent_acc) - This example presents that

ID_ACC:   18252,
          34456,
          456567 

has same parent_acc (76543).

Some of childs has info (info table)

(This example presents that 18252 and 34456 are connect with info table)

Next, some info.account_id contain product (Product table) (in This example - only 18252 accountid has product which has account number:456452.)

Account.id_acc=Info.account_id
Info.product_id=Product_id.product_id

I would like to select all accounts hierarchy:

76543,
18252,
34456,
456567,
456452.

The hardest (for me) thing is that I need to base on table (to show). If to_show table is filled by eg. 34456 then accounts output need to be the same like above list.

If to_show table is filled by eg. "76543,18252,34456" then same situation as above, and so on...

All what I need is to present all hierarchy regardless which of accounts (in product table or info or account) are in to_show table... As input I can get child/parent or account from product table. I was trying to do that, but unfortanely it is too hard for me (as I'm totally newbie in hierarchy queries..). Guys, could you help me?

create table account (
id_acc number,
parent_acc number
)
;
create table info
(
ID number,
Account_id number,
Product_id number
);
create table product (
Account_id number,
product_id number
);
create table to_show (
ID number
);
insert into account (id_acc,parent_acc)) values (76543,76543);
insert into account (id_acc,parent_acc) values (18252,76543);
insert into account (id_acc,parent_acc) values (34456,76543);
insert into account (id_acc,parent_acc) values (456567,76543);
insert into account (id_acc,parent_acc) values (457564,65424);
insert into account (id_acc,parent_acc) values (45654242,6556756);
insert into info values (875621,18252,98567);
insert into info values (875621,34456,null);
insert into product values (456452,98567);
insert into to_show values (34456);
bazyl
  • 263
  • 1
  • 7
  • 17
  • 1
    I would love to help; but I have no idea what you're really after. This doesn't seem to be a hierarchy to me; but a list of all accounts related to 76543. Is that what you're really after? all the accounts related to 76543 in some fashion even if it's though a product relationship? – xQbert Nov 23 '15 at 16:16
  • I think you say "hierarchy" instead of something else. Please think of a better word for it. Otherwise, the question is clear and detailed; we just don't see what you want to achieve. – Koshinae Nov 23 '15 at 16:22
  • Why doesn't 45654242 appear in your results? I see no reason in the question stated to exclude them? Is it because it's not a child of 76543? – xQbert Nov 23 '15 at 16:49
  • That's only an example of data. As input I have table to_show which will contain accounts id which I want to display (of course including all hierarchy). Maybe hierarchy is a bad word for that - all "tree" - I think it is better ; ) – bazyl Nov 23 '15 at 18:06
  • xQbert: 45654242 can't appear because as input I have account: 34456 . And account 34456 is no relate with 45654242 – bazyl Nov 23 '15 at 18:07
  • @user5506560 Yeah I completely missed the to_Show concept in my initial pass on this. I believe the update now should address all known issues at this time. – xQbert Nov 24 '15 at 14:39

1 Answers1

3

An approach may be to first union the missing hierarchy from product and info into account and then generate the results... Since Product and info have relevant data to the hierarchy we need to combine them. I think this is likely the part that was the most troubling. Not all the hierarchy data was in one place. Once I realized that, the need for a union to allow the connect by prior to work was born.

So we join product to info to get the needed additional hierarchical data and union this to the account table. This results in a dataset containing all the needed parent child relationships.

We can then use connect by prior to traverse the hierarchy.

SELECT a.id_acc, level as lvl
FROM (SELECT id_acc, parent_acc FROM account 
      UNION 
      SELECT P.Account_ID, i.account_id  FROM info i
      INNER JOIN product p 
       on I.Product_ID=P.Product_ID) A
START WITH A.ID_ACC=76543
CONNECT BY prior  a.id_acc=a.parent_acc

This would return:

Account Lvl
76543   1
18252   2
456452  3
34456   2
456567  2

Note: 456452 exists under 18252 so the relationship from the hierarchy is maintained; implying 76543 has 3 children (18252, 34456, 456567) and 18252 has a child 456452.

I think this is the best I can come up with given some assumptions about your question.

You could add some other bells and whistles to it if you need to know full path (sys_connect_by_path) or if you have loops in the data you need to eliminate (nocycle) or isLeaf (connect_by_isLeaf) to show childless records in the hierarchy.

SELECT a.id_acc
     , level as lvl
     , SYS_CONNECT_BY_PATH(ID_Acc, '/') "Path"
     , CONNECT_BY_ISLEAF "IsLeaf"
FROM (SELECT id_acc, parent_acc FROM account 
      UNION
      SELECT P.Account_ID, i.account_id  
      FROM info i
      INNER JOIN product p 
        on I.Product_ID=P.Product_ID) A
START WITH a.id_acc=76543
CONNECT BY nocycle prior a.id_acc=a.parent_acc

This would result in something like:

ID_acc  lvl Path                  isLeaf
76543   1   /76543                0
18252   2   /76543/18252          0
456452  3   /76543/18252/456452   1
34456   2   /76543/34456          1
456567  2   /76543/456567         1

For a full list of features see Oracle Docs

I kind of downplayed the importance of not having loops in the data by using nocycle. Your results may be incorrect should loops in the data exist.

With a bit more work we can determine the top levels involved in to_Show though this:

select distinct ID_ACC
from (SELECT id_acc, parent_acc FROM account 
      UNION
      SELECT P.Account_ID, i.account_id  
      FROM info i
      inner join product p 
        on i.product_id=p.product_id) a
where parent_acc is null
connect by  prior a.parent_acc = a.id_acc
start with id_acc in (select id from to_show)

and combine it with the above to achieve the desired results with no hardcoding.

SELECT a.id_acc
     , level as lvl
     , SYS_CONNECT_BY_PATH(ID_Acc, '/') "Path"
     , connect_by_isleaf "IsLeaf"
FROM (SELECT id_acc, parent_acc FROM account 
      UNION
      SELECT P.Account_ID, i.account_id  
      FROM info i
      INNER JOIN product p 
        on i.product_id=p.product_id) a
connect by nocycle prior a.id_acc=a.parent_acc
start with id_acc in 
(
select distinct ID_ACC
from (SELECT id_acc, parent_acc FROM account 
      UNION
      SELECT P.Account_ID, i.account_id  
      FROM info i
      inner join product p 
        on i.product_id=p.product_id) a
where parent_acc is null
connect by  prior a.parent_acc = a.id_acc
start with id_acc in (select id from to_show))

So all we did here was use start with in a subquery to get the top level domain of each record in to_Show, and invert the relationship so we crawled up the tree instead of down it and then used that distinct top level domain to return the entire hierarchy; thus showing the nodes in to_show; and the entire hierarchy in which it resides.

enter image description here

or as a CTE (Common Table Expression) for readability and refactoring to eliminate duplication.

with 
--Let's combine the hierarchies so they are all in one place.
hsource as (
   select id_acc, parent_acc from account 
   UNION
   SELECT P.Account_ID, i.account_id  
   FROM info i
   inner join product p 
   on i.product_id=p.product_id),
--Now lets get the top domain or root nodes of each item in To_Show
top_domain as (
   select distinct id_acc  
   from (hSource) a
   where parent_acc is null  --this null compare is actually what tells us it's a root/Top domain!  Assumption being all roots have null parent_Acc!
   connect by  prior a.parent_acc = a.id_acc
   start with id_acc in (select id from to_show))
--Now lets use those top domains or root notes to render the hierarchy of each.
SELECT a.id_acc
     , level as lvl
     , SYS_CONNECT_BY_PATH(ID_Acc, '/') "Path"
     , connect_by_isleaf "IsLeaf"
FROM (Select id_acc, parent_acc from hSource) a
connect by nocycle prior a.id_acc=a.parent_acc
start with id_acc in  (Select ID_ACC from Top_Domain)

Lastly... If my assumption that the parent_Acc is null is incorrect then you'll need to change the query in top_domain to the below: Connect by prior assumes that the top level will be null otherwise you get looping data which is why you can't simply set a.ID_ACC=a.parent_Acc, so we just alter two statements data using case statements to make a Parent_ACC is null to be a true statement. Technically the where clause could just be ID_ACC=parent_Acc, but I wanted to keep with the theme. The connect by prior line MUST be changed however. Remember up above when I said no loops in data... Well when ID_ACC=parent_ACC that causes a loop. I mean come on! how can I be my own parent? But we handled it using case statements.

with 
--Let's combine the hierarchies so they are all in one place.
hsource as (
   select id_acc, parent_acc from account 
   UNION
   SELECT P.Account_ID, i.account_id  
   FROM info i
   inner join product p 
   on i.product_id=p.product_id),
--Now lets get the top domain or root nodes of each item in To_Show
top_domain as (
   select distinct a.id_acc
   from (hsource) a
--Modification starts here...
   where case when  a.parent_acc =a.id_Acc then null else a.parent_Acc end is null
   connect by prior (case when a.parent_acc=a.id_acc then null else a.parent_Acc end) = a.id_acc
--ends here
   start with a.id_acc in (select id from to_show))
--Now lets use those top domains or root notes to render the hierarchy of each.
    SELECT a.id_acc
         , level as lvl
         , SYS_CONNECT_BY_PATH(ID_Acc, '/') "Path"
         , connect_by_isleaf "IsLeaf"
    FROM (Select id_acc, parent_acc from hSource) a
    connect by nocycle prior a.id_acc=a.parent_acc
    start with id_acc in  (Select ID_ACC from Top_Domain)
xQbert
  • 34,733
  • 2
  • 41
  • 62
  • xQbert - thanks for reply : ) But I have to avoid hardcoding. You used a.id_acc = 76543. As I mentioned as input I will get table to_show. Data in table account,product,info were only an example to present what I want to achieve. – bazyl Nov 23 '15 at 18:08
  • Let's assume that table to_show is filled by "34456" . Base on that account I need to show all hierarchy/account tree. Output will present the same values as yours code. But like i mentioned - unfortanely I can't use hard-coding... I don't know which accounts will be in to_show table. – bazyl Nov 23 '15 at 18:10
  • @user5506560 lunch... enhanced to include to_show. I'm assuming the values in to_Show could be at any level in the hierarchy and you want us to to go to the top of each of those hierarchies and return the entire tree. – xQbert Nov 23 '15 at 19:33
  • I also refactored the code to a Common Table Expression to reduce redundancy and improve readability. Yet the results remain the same as the screenshot! – xQbert Nov 23 '15 at 19:50
  • xQbert - U are the best : )It is really helpfull... : ) I have one more question - Assume that instead of null in Account table (for 76543 ID) I will have the same number 76543. So the only difference is 76543 instead of null. Still that account is a parent. How should I change the code? I was trying to change "where parent_acc is null" on where parent_acc = a.id_acc but It doesn't work. – bazyl Nov 24 '15 at 08:24
  • I edited my question (data in table account). Could you take a look? – bazyl Nov 24 '15 at 09:51
  • @user5506560 updated. Connect by prior assumes top domain parent is null. Since this isn't the case... I used a case statement to make it the case! :P Case solved. In case you didn't get it I was trying to be humorous. Lines starting --Modifications starts here --ends here tell you where the change was made. – xQbert Nov 24 '15 at 14:34