Question

I've been working on this problem on and off for a few days now, and I'm getting nowhere.

I have a dataset that resembles this:

NAME                EXPIRATION          PARENT_ID    CLASS_ID           
Master Class           365                              1               
Second Class           366                  1           2                 
Third Class            355                  2           3                 
Fourth Class           1001                 2           4                 
Fifth Class            1000                 4           5
Sixth Class            999                  4           6  

ect. ect.

I can use a hierarchical query to get a view of what classes are required under a certain class.

What I really want to know is what the minimum expiration date is for the current level and sub-levels of the hierarchy. The expiration is the minimum expiration of anything under it.

If I wanted to do this in a brute force manner, I could get the results of my hierarchical query back, then for each line run a query that looks like this:

select min(expiration_date)
from ( start with class_id = $EACH_CLASS_ID_FROM_PREVIOUS_QUERY
connect by prior parent_id = class_id);

I'm picturing the result like this:

NAME             EXPIRATION                           CLASS_ID
Master Class      355 (Min of it or anything under it)     1
Second Class      355  ""                                  2
Third Class       355  ""                                  3
Fourth Class      999  ""                                  4
Fifth Class       1000 ""                                  5
Sixth Class       999  ""                                  6

I'm assuming there's a better way though? Maybe?

Thanks for any help, I've been puzzling over this for a few days now.

Was it helpful?

Solution

Your table:

SQL> create table mytable (name,expiration,parent_id,class_id)
  2  as
  3  select 'Master Class', 365, null, 1 from dual union all
  4  select 'Second Class', 366, 1, 2 from dual union all
  5  select 'Third Class', 355, 2, 3 from dual union all
  6  select 'Fourth Class', 1001, 2, 4 from dual union all
  7  select 'Fifth Class', 1000, 4, 5 from dual union all
  8  select 'Sixth Class', 999, 4, 6 from dual
  9  /

Table created.

Using the good old connect by syntax:

SQL> with t as
  2  ( select connect_by_root class_id as class_id
  3         , connect_by_root name as name
  4         , expiration
  5      from mytable
  6   connect by parent_id = prior class_id
  7  )
  8  select class_id
  9       , name
 10       , min(expiration)
 11    from t
 12   group by class_id
 13       , name
 14   order by class_id
 15  /

  CLASS_ID NAME         MIN(EXPIRATION)
---------- ------------ ---------------
         1 Master Class             355
         2 Second Class             355
         3 Third Class              355
         4 Fourth Class             999
         5 Fifth Class             1000
         6 Sixth Class              999

6 rows selected.

And if you are on 11g Release 2 or higher, you can use recursive subquery factoring:

SQL> with all_paths (root_class_id,root_name,class_id,expiration) as
  2  ( select class_id
  3         , name
  4         , class_id
  5         , expiration
  6      from mytable
  7     union all
  8    select ap.root_class_id
  9         , ap.root_name
 10         , t.class_id
 11         , t.expiration
 12      from mytable t
 13           inner join all_paths ap on (t.parent_id = ap.class_id)
 14  )
 15  select root_class_id as class_id
 16       , root_name as name
 17       , min(expiration)
 18    from all_paths
 19   group by root_class_id
 20       , root_name
 21   order by class_id
 22  /

  CLASS_ID NAME         MIN(EXPIRATION)
---------- ------------ ---------------
         1 Master Class             355
         2 Second Class             355
         3 Third Class              355
         4 Fourth Class             999
         5 Fifth Class             1000
         6 Sixth Class              999

6 rows selected.

OTHER TIPS

If you want to find minimum expiration date as part of hierarchical query then you could try something like this:

select t.*,
       min(t.expiration) 
       over(partition by connect_by_root class_id) min_expiration_date
  from your_table t
connect by prior t.class_id = t.parent_id;

To get distinct records wrap it with another query:

select q.*
  from (select t.*,
               min(t.expiration) 
               over(partition by connect_by_root class_id) min_expiration_date,
               level lvl
          from your_table t
        connect by prior t.class_id = t.parent_id) q
 where q.lvl = 1;

The benefit of this approach is that you can use different WHERE conditions in the outer query to get any desired result. In above example your resultset is only root nodes. If you slightly modify it you can get a full tree instead (preserving minimum expiration value as well):

select q.*
  from (select t.*,
               min(t.expiration) 
               over(partition by connect_by_root class_id) min_expiration_date,
               connect_by_root class_id root_node
          from your_table t
        connect by prior t.class_id = t.parent_id) q
 where q.root_node = 2;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top