Materialized Views

Nested Materialized Views

Sometimes a single materialized view will not meet our requirements. For example, given this base table

select t_key, amt, key
from t2
order by t_key, amt, key ;
 
     T_KEY        AMT        KEY
---------- ---------- ----------
         1        100         10
         1        200         30
         1        300         20
         2        150         50
         2        250         40
 

say we wanted a fast refreshable materialized view defined with the following query.

select
  t_key    t_key ,
  max(amt) amt_max ,
  max(key) keep ( dense_rank last order by amt ) as t2_key_of_amt_max
from
  t2
group by
  t_key
;
 
     T_KEY    AMT_MAX T2_KEY_OF_AMT_MAX
---------- ---------- -----------------
         1        300                20
         2        250                40
 

(T2_KEY_OF_AMT_MAX identifies the KEY value associated with the highest AMT value in each group.)

As always the first step is to create a materialized view log on T2.

create materialized view log on t2
  with
    rowid ,
    sequence
    ( key, t_key, amt )
  including new values
;
 

Now let's see what the MY_MV_CAPABILITIES utility (created in topic DBMS_MVIEW.EXPLAIN_MVIEW) tells us about our query.

set long 5000

select
  my_mv_capabilities(
'select
  t_key    t_key ,
  max(amt) amt_max ,
  max(key) keep ( dense_rank LAST order by amt ) as t2_key_of_amt_max
from
  t2
group by
  t_key'
  , 'REFRESH' ) as mv_report
from dual ;
 
MV_REPORT
--------------------------------------------------------------------------------

Capable of:

  REFRESH_COMPLETE

Not Capable of:

  REFRESH_FAST

  REFRESH_FAST_AFTER_INSERT
    aggregate function nested within an expression

  REFRESH_FAST_AFTER_ONETAB_DML
    see the reason why REFRESH_FAST_AFTER_INSERT is disabled

  REFRESH_FAST_AFTER_ONETAB_DML
    mv uses the MIN or MAX aggregate functions

  REFRESH_FAST_AFTER_ANY_DML
    see the reason why REFRESH_FAST_AFTER_ONETAB_DML is disabled
 

Though not entirely obvious from the report, it turns out our query is not fast refreshable because the LAST aggregate function which we used to implement T2_KEY_OF_AMT_MAX is not one of the fast refreshable aggregates SUM, COUNT, AVG, STDDEV, VARIANCE, MIN and MAX (see Restrictions on Fast Refresh on Materialized Views with Aggregates).

Let's try writing the query using a subquery instead of LAST.

select
  my_mv_capabilities(
'select
  t_key    t_key ,
  max(amt) amt_max ,
  max(key) as t2_key_of_amt_max
from
  t2
where
  ( t_key, t2.amt ) in
    (
      select t_key, max(amt)
      from   t2
      group by t_key
    )
group by
  t_key'
  , 'REFRESH' ) as mv_report
from dual ;
 
MV_REPORT
--------------------------------------------------------------------------------

Capable of:

  REFRESH_COMPLETE

Not Capable of:

  REFRESH_FAST

  REFRESH_FAST_AFTER_INSERT
    subquery in mv

  REFRESH_FAST_AFTER_ONETAB_DML
    see the reason why REFRESH_FAST_AFTER_INSERT is disabled

  REFRESH_FAST_AFTER_ONETAB_DML
    mv uses the MIN or MAX aggregate functions

  REFRESH_FAST_AFTER_ANY_DML
    see the reason why REFRESH_FAST_AFTER_ONETAB_DML is disabled
 

It looks like a subquery will not work either. Perhaps an analytic approach will work?

select
  my_mv_capabilities(
'select distinct
  t_key    t_key ,
  max( amt ) over ( partition by t_key ) as amt_max ,
  last_value( key )
    over
    (
      partition by t_key
      order by amt
      range between unbounded preceding and unbounded following
    )
    as t2_key_of_amt_max
from
  t2'
  , 'REFRESH' ) as mv_report
from dual ;
 
MV_REPORT
--------------------------------------------------------------------------------

Capable of:

  REFRESH_COMPLETE

Not Capable of:

  REFRESH_FAST

  REFRESH_FAST_AFTER_INSERT
    DISTINCT clause in select list in mv

  REFRESH_FAST_AFTER_INSERT
    DISTINCT clause in select list in mv

  REFRESH_FAST_AFTER_INSERT
    window function in mv

  REFRESH_FAST_AFTER_ONETAB_DML
    see the reason why REFRESH_FAST_AFTER_INSERT is disabled

  REFRESH_FAST_AFTER_ANY_DML
    see the reason why REFRESH_FAST_AFTER_ONETAB_DML is disabled
 

This last approach did not work either, which is a bit of a relief actually since the technique is rather crass. We need to rethink our approach.

Since deriving the desired result set is conceptually a three step process

  1. find the highest AMT value
  2. find the highest KEY value per AMT
  3. join the results of steps (1) and (2) together on the AMT column

perhaps three separate materialized views would work? The materialized views for steps 1 and 2, which we will call MV1 and MV2, can be based on table T2 and can be refreshed independently of each other. However the materialized view for step 3, which we will call MV3, will need to be based on MV1 and MV2 and will need to refresh after they do.

Fortunately Oracle allows for a materialized view like MV3 and automatically manages the refresh order when all three views are refreshable on commit. Materialized views like MV3 are called "Nested Materialized Views". Note the term "Nested Materialized View" does not refer to MV1 and MV2, even though they could be thought of as being "nested" within MV3.

Restrictions and Recommendations

As always, before creating a type of materialized view we have not tried before we must be aware of its restrictions. For nested materialized views they are these.

  • The base materialized views must contain joins or aggregates.
  • The defining query must contain joins or aggregates.
  • All base objects, whether they are tables or materialized views, must each have materialized view logs.
  • If REFRESH FAST is specified then all materialized views in any chain related to the materialized view must also specify REFRESH FAST.

Note that all base objects in a nested materialized view, regardless of whether they are tables or materialized views, are treated as tables.

We are now ready to craft our three step solution.

create materialized view MV1
  refresh fast on commit
  as
    select
      t_key ,
      max(amt)   amt_max ,
      count(amt) amt_count ,
      count(*)   row_count
    from t2
    group by t_key
;

create materialized view log on mv1
  with
    rowid ,
    sequence
    ( t_key, amt_max, amt_count, row_count )
  including new values
;

select
  my_mv_capabilities( 'MV1', 'REFRESH' ) as mv_report
from dual ;
 
MV_REPORT
--------------------------------------------------------------------------------

Capable of:

  REFRESH_COMPLETE

  REFRESH_FAST

  REFRESH_FAST_AFTER_INSERT

  REFRESH_FAST_AFTER_ANY_DML
 
create materialized view MV2
  refresh fast on commit
  as
    select
      t_key ,
      amt ,
      max(key)   max_key_per_amt ,
      count(*)   row_count
    from t2
    group by t_key, amt
;

create materialized view log on mv2
  with
    rowid ,
    sequence
    ( t_key, max_key_per_amt, row_count )
  including new values
;

select
  my_mv_capabilities( 'MV2', 'REFRESH' ) as mv_report
from dual ;
 
MV_REPORT
--------------------------------------------------------------------------------

Capable of:

  REFRESH_COMPLETE

  REFRESH_FAST

  REFRESH_FAST_AFTER_INSERT

  REFRESH_FAST_AFTER_ANY_DML
 
create materialized view MV3
  refresh fast on commit
  as
    select
      mv1.t_key ,
      mv1.amt_max ,
      mv2.max_key_per_amt as t2_key_of_amt_max ,
      mv1.rowid mv1_rowid ,
      mv2.rowid mv2_rowid
    from  mv1, mv2
    where mv1.t_key = mv2.t_key
    and   mv1.amt_max = mv2.amt
;

select
  my_mv_capabilities( 'MV3', 'REFRESH' ) as mv_report
from dual ;
 
MV_REPORT
--------------------------------------------------------------------------------

Capable of:

  REFRESH_COMPLETE

  REFRESH_FAST

  REFRESH_FAST_AFTER_INSERT

  REFRESH_FAST_AFTER_ONETAB_DML

  REFRESH_FAST_AFTER_ANY_DML
 

We finally have a fast refreshable materialized view solution. Let's confirm that MV3, the nested one, contains the correct results.

select t_key, amt_max, t2_key_of_amt_max
from mv3
order by t_key ;
 
     T_KEY    AMT_MAX T2_KEY_OF_AMT_MAX
---------- ---------- -----------------
         1        300                20
         2        250                40
 

Good. It matches the results returned by the first query we tried which used the LAST function. Now let's put all three materialized views through their paces. First we perform a few mixed DML transactions.

insert into t2 values ( 60, 3, 450 );
insert into t2 values ( 70, 3, 550 );

update t2 set amt = 300 where key = 30 ;
commit;

delete from t2 where key = 70 ;
update t2 set amt = 650 where key = 60 ;
commit;

select t_key, amt, key
from t2
order by t_key, amt, key ;
 
     T_KEY        AMT        KEY
---------- ---------- ----------
         1        100         10
         1        300         20
         1        300         30
         2        150         50
         2        250         40
         3        650         60
 

Now we check MV3 to see if it contains the correct info.

select t_key, amt_max, t2_key_of_amt_max
from mv3
order by t_key ;
 
     T_KEY    AMT_MAX T2_KEY_OF_AMT_MAX
---------- ---------- -----------------
         1        300                30
         2        250                40
         3        650                60
 

It does. Mission accomplished.

Cleanup

drop materialized view mv1 ;
drop materialized view mv2 ;
drop materialized view mv3 ;

drop materialized view log on t2 ;

delete t2 ;
insert into t2 select * from t2_backup ;
commit;
 



Linking to SQL Snippets ™

To link to this page in Oracle Technology Network Forums or OraFAQ Forums cut and paste this code.

  • [url=http://www.sqlsnippets.com/en/topic-12926.html]SQL Snippets: Materialized Views - Nested Materialized Views[/url]

To link to this page in HTML documents or Blogger comments cut and paste this code.

  • <a href="http://www.sqlsnippets.com/en/topic-12926.html">SQL Snippets: Materialized Views - Nested Materialized Views</a>

To link to this page in other web sites use the following values.

  • Link Text : SQL Snippets: Materialized Views - Nested Materialized Views
  • URL (href): http://www.sqlsnippets.com/en/topic-12926.html