Skip to Main Content
  • Questions
  • How to write a SQL to join with multiple tables and select results in a query from any on of the table (like in queue)

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Anu.

Asked: October 18, 2017 - 9:43 am UTC

Last updated: October 19, 2017 - 10:35 am UTC

Version: 12c

Viewed 50K+ times! This question is

You Asked

Hi , I have following tables . I am getting orders data from various sources in table test_orders. Then joining the items to various table for revenue percentage and then send the orders along with revenue information to another 3rd party tool for revenue recognition.
My goal is to have outbound_rev_orders table populated as given below using insert statements. but before that I need to get Item child information
if item child is present in following order :
1) single_items
2) parent_child_items_set1
3) product_child_items

If item is present only in table then , I can right a quesry using outer join and achieve the results in a single query. But here few items are present in parent_child_items_set1 and product_child_items tables. In such cases , I want to select details only from parent_child_items_set1 and ignore data from product_child_items . How do I achieve this using single SQL query. I know in pl/sql it is possible to achieve that but it's get little complicated , so exploring if single SQL query can do this ?

---drop table test_orders;

create table test_orders ( order_id number  , item_number  varchar(25),   rev_acct_number number, unit_sell_price number , qty number   ) ; 
 
 insert into test_orders  values ( 100, 'ITEM-A',  1001 , 250, 2 );
 
 insert into test_orders  values ( 100, 'ITEM-B',  1001 , 500, 10 );
 
 insert into test_orders  values ( 100, 'ITEM-C',  1001 , 100, 10 );
 
 insert into test_orders  values ( 100, 'ITEM-D',  2222 , 300, 10 );
 
 insert into test_orders  values ( 100, 'ITEM-E',  2001 , 200, 20 );
 
 --- Item tables 
 ---drop table single_items
 
 create table single_items ( item_number varchar2(25) , rev_acct_number number , attribute1 varchar2(25) );
 
 insert into single_items values ( 'ITEM-A', 1001, 'REV ALLOCATION A' );
 
--- drop table parent_child_items_set1
 
 create table parent_child_items_set1  ( item_number varchar2(25) , rev_acct_number number ,item_child varchar2(25), attribute1 varchar2(25), rev_prct number  );
 
 insert into parent_child_items_set1 values ( 'ITEM-B', 1001, 'ITEM-B-CHILD1', 'REV ALLOCATION B1', 0.25 );
 
 insert into parent_child_items_set1 values ( 'ITEM-B', 1002, 'ITEM-B-CHILD2', 'REV ALLOCATION b2', 0.75 );
 
 insert into parent_child_items_set1 values ( 'ITEM-C', 1001, 'ITEM-C-CHILD1', 'REV ALLOCATION C1', 0.45 );
  
 insert into parent_child_items_set1 values ( 'ITEM-C', 1002, 'ITEM-C-CHILD2', 'REV ALLOCATION C2', 0.55 );
 
 
--- drop table product_child_items;

create table product_child_items  ( item_number varchar2(25) , rev_acct_number number ,item_child varchar2(25), attribute1 varchar2(25), rev_prct number  );
  
 
 insert into product_child_items values ( 'ITEM-C', 1001, 'ITEM-C-CHILD1', 'REV ALLOCATION C1', 0.30 );
 
 insert into product_child_items values ( 'ITEM-C', 1002, 'ITEM-C-CHILD2', 'REV ALLOCATION C2', 0.35 );
 
 insert into product_child_items values ( 'ITEM-C', 1003, 'ITEM-C-CHILD3', 'REV ALLOCATION C3', 0.35 );
 
 insert into product_child_items values ( 'ITEM-E', 2001, 'ITEM-E-CHILD1', 'REV ALLOCATION E1', 0.50 );
 
 insert into product_child_items values ( 'ITEM-E', 2002, 'ITEM-E-CHILD2', 'REV ALLOCATION E2', 0.50 );
 
 ----drop table outbound_rev_orders

 create table outbound_rev_orders ( order_id number  , item_number  varchar(25), item_child varchar2(25), attribute1  varchar2(25),  rev_acct_number number, 
 unit_sell_price number , qty number,  rev_prct number   ) ; 
 
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-A', null,  'REV ALLOCATION A', 1001 , 250, 2, null  );
 
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-B', 'ITEM-B-CHILD1','REV ALLOCATION B1', 1001 , 500, 10, 0.25  );
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-B', 'ITEM-B-CHILD2','REV ALLOCATION B2', 1002 , 500, 10, 0.75 );
 
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-C', 'ITEM-C-CHILD1','REV ALLOCATION C1', 1001 , 500, 10, 0.30  );
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-C', 'ITEM-C-CHILD2','REV ALLOCATION C2', 1002 , 500, 10, 0.35 );
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-C', 'ITEM-C-CHILD3','REV ALLOCATION C3', 1003 , 500, 10, 0.35 );
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-D', null ,null, 1003 , 500, 10, null );
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-E', 'ITEM-E-CHILD1', 'REV ALLOCATION E1', 1003 , 500, 10, 0.5 );
 
 insert into outbound_rev_orders  values ( 100, 'ITEM-E', 'ITEM-E-CHILD2', 'REV ALLOCATION E2', 1003 , 500, 10, 0.5 );

commit;


Thanks
Anu

and Chris said...

If you want to select columns from different tables with a priority, you can:

- (full) outer join all the tables
- Use coalesce to return the first non-null column from these tables in the desired order

Plugging this into your data gives something like:

select tor.order_id,
       coalesce ( sit.item_number, pcis.item_number, pci.item_number, tor.item_number) item,
       coalesce ( sit.rev_acct_number, pcis.rev_acct_number, pci.rev_acct_number, tor.rev_acct_number) acct,
       coalesce ( sit.attribute1, pcis.attribute1, pci.attribute1) attr1,
       coalesce ( pcis.rev_prct, pci.rev_prct) prct
from   test_orders tor
full join single_items sit
on    tor.item_number = sit.item_number
and   sit.rev_acct_number = tor.rev_acct_number
full join parent_child_items_set1 pcis
on    pcis.item_number = tor.item_number
and   pcis.rev_acct_number = tor.rev_acct_number
full join product_child_items pci
on   pci.item_number = tor.item_number
and  pci.rev_acct_number = tor.rev_acct_number
order  by item, acct;

ORDER_ID   ITEM     ACCT   ATTR1               PRCT   
       100 ITEM-A     1001 REV ALLOCATION A           
       100 ITEM-B     1001 REV ALLOCATION B1     0.25 
           ITEM-B     1002 REV ALLOCATION b2     0.75 
       100 ITEM-C     1001 REV ALLOCATION C1     0.45 
           ITEM-C     1002 REV ALLOCATION C2     0.35 
           ITEM-C     1002 REV ALLOCATION C2     0.55 
           ITEM-C     1003 REV ALLOCATION C3     0.35 
       100 ITEM-D     2222                            
       100 ITEM-E     2001 REV ALLOCATION E1      0.5 
           ITEM-E     2002 REV ALLOCATION E2      0.5


This has doubled up ITEM-C, acct 1002. I'm not sure exactly how your tables are joined, so it may be a mistake there. If the joins are correct, you can remove the extra rows by:

- Assigning a row_number for each item and acct, based on the order source table and the order you want to prioritise these
- Selecting the first row_number in each group

You can use coalesce again to ensure you partition by item and acct. And map the tables to a number matching your priority order in the order by clause.

For example:

row_number() over (
  partition by 
    coalesce ( sit.item_number, pcis.item_number, pci.item_number, tor.item_number),
    coalesce ( sit.rev_acct_number, pcis.rev_acct_number, pci.rev_acct_number, tor.rev_acct_number)
  order by coalesce (
    case when sit.item_number is not null then 1 end,
    case when pcis.item_number is not null then 2 end,
    case when pci.item_number is not null then 3 end
  )
)


Put it all together and you get:

with rws as (
select tor.order_id,
       coalesce ( sit.item_number, pcis.item_number, pci.item_number, tor.item_number) item,
       coalesce ( sit.rev_acct_number, pcis.rev_acct_number, pci.rev_acct_number, tor.rev_acct_number) acct,
       coalesce ( sit.attribute1, pcis.attribute1, pci.attribute1) attr1,
       coalesce ( pcis.rev_prct, pci.rev_prct) prct,
       row_number() over (
         partition by 
           coalesce ( sit.item_number, pcis.item_number, pci.item_number, tor.item_number),
           coalesce ( sit.rev_acct_number, pcis.rev_acct_number, pci.rev_acct_number, tor.rev_acct_number)
         order by coalesce (
           case when sit.item_number is not null then 1 end,
           case when pcis.item_number is not null then 2 end,
           case when pci.item_number is not null then 3 end
         )
       ) rn
from   test_orders tor
full join single_items sit
on    tor.item_number = sit.item_number
and   sit.rev_acct_number = tor.rev_acct_number
full join parent_child_items_set1 pcis
on    pcis.item_number = tor.item_number
and   pcis.rev_acct_number = tor.rev_acct_number
full join product_child_items pci
on   pci.item_number = tor.item_number
and  pci.rev_acct_number = tor.rev_acct_number
)
  select * from rws
  where  rn = 1
  order by item, acct;

ORDER_ID   ITEM     ACCT   ATTR1               PRCT   RN   
       100 ITEM-A     1001 REV ALLOCATION A              1 
       100 ITEM-B     1001 REV ALLOCATION B1     0.25    1 
           ITEM-B     1002 REV ALLOCATION b2     0.75    1 
       100 ITEM-C     1001 REV ALLOCATION C1     0.45    1 
           ITEM-C     1002 REV ALLOCATION C2     0.55    1 
           ITEM-C     1003 REV ALLOCATION C3     0.35    1 
       100 ITEM-D     2222                               1 
       100 ITEM-E     2001 REV ALLOCATION E1      0.5    1 
           ITEM-E     2002 REV ALLOCATION E2      0.5    1


This still leaves you with some blank order_ids to "fill down". I'm not sure what's supposed to happen if an item appears on multiple orders, so I'll leave this as an exercise for the reader ;)

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

A reader, October 19, 2017 - 5:37 am UTC


Thanks Chris!

A reader, October 19, 2017 - 5:50 am UTC

Hi ,

As I said, yes I can achieve the needed results using outer joins when Items are exclusive to that table only.
But here ITEM-C is both of these tables :
1)parent_child_items_set1
ITEM-c -- TWO Rows
2) product_child_items
ITEM-C --- Three Rows
All my joins are only on Item Number , based on above data, I want results to have only ITEM-c -- TWO Rows from parent_child_items_set1 . I do not want to get any row from product_child_items table for ITEM-C.
so output desired is ....
ORDER_ID ITEM ACCT ATTR1 PRCT
100 ITEM-A 1001 REV ALLOCATION A
100 ITEM-B 1001 REV ALLOCATION B1 0.25
ITEM-B 1002 REV ALLOCATION b2 0.75
100 ITEM-C 1001 REV ALLOCATION C1 0.45
ITEM-C 1002 REV ALLOCATION C2 0.55
100 ITEM-D 2222
100 ITEM-E 2001 REV ALLOCATION E1 0.5
ITEM-E 2002 REV ALLOCATION E2 0.5

Thanks
Anu

Chris Saxon
October 19, 2017 - 10:35 am UTC

So you want to exclude 'ITEM-C-CHILD3'? Or just have parent_child_items_set1 take priority over product_child_items for children 1 & 2?

In any case, play with the partition by in the row_number function so you're grouping at the correct level. If you only want to consider by item_number, you may need to change this to rank() to ensure you get all the children.

More to Explore

Analytics

Analytic SQL got you confused? Check out Connor McDonald's complete video course.