Materialized Views
REFRESH FAST
Now that we know how materialized view logs track changes to base tables we can use them to perform fast materialized view refreshes, i.e. refreshes where only the individual materialized view rows affected by base table changes are updated. This is also called "incremental" refreshing.
Earlier in this tutorial we saw how the rowids for each row in a materialized view changed after a complete refresh. Now let's see what happens to a materialized view's rowids after a fast refresh. First we use the REFRESH FAST clause to specify that the default refresh method should be fast.
create materialized view log on t with sequence ;
create materialized view mv
REFRESH FAST
as select * from t
;
select key, val, rowid
from mv ;
KEY VAL ROWID
---------- ----- ------------------
1 a AAAWm+AAEAAAAaMAAA
2 b AAAWm+AAEAAAAaMAAB
3 c AAAWm+AAEAAAAaMAAC
4 AAAWm+AAEAAAAaMAAD
Now we refresh the materialized view. The "F" value for the "method" parameter ensures the refresh will be a Fast one.
execute dbms_mview.refresh( list => 'MV', method => 'F' );
select key, val, rowid
from mv ;
KEY VAL ROWID
---------- ----- ------------------
1 a AAAWm+AAEAAAAaMAAA
2 b AAAWm+AAEAAAAaMAAB
3 c AAAWm+AAEAAAAaMAAC
4 AAAWm+AAEAAAAaMAAD
The rowids did not change. Thus, with a fast refresh the materialized view data is not touched when no changes have been made to the base table, unlike a complete refresh where each row would have been created anew.
Now let's update a row in the base table.
update t set val = 'XX'
where key = 3 ;
commit;
execute dbms_mview.refresh( list => 'MV', method => 'F' );
select key, val, rowid
from mv ;
KEY VAL ROWID
---------- ----- ------------------
1 a AAAWm+AAEAAAAaMAAA
2 b AAAWm+AAEAAAAaMAAB
3 XX AAAWm+AAEAAAAaMAAC
4 AAAWm+AAEAAAAaMAAD
Still no change in the rowids. In row 3 we can see that VAL changed from "c" to "XX" though, telling us that row 3 was updated during the refresh.
Defaults
The REFRESH FAST clause of the CREATE MATERIALIZED VIEW command tells Oracle what type of refresh to perform when no refresh option is specified. A materialized view created with REFRESH FAST can still be refreshed completely if required though. In the following example note how, even though MV was created above with the REFRESH FAST clause, all its rowids change after the refresh. This indicates that a complete refresh was performed.
execute dbms_mview.refresh( list => 'MV', method => 'C' );
select key, val, rowid
from mv ;
KEY VAL ROWID
---------- ----- ------------------
1 a AAAWm+AAEAAAAaMAAE
2 b AAAWm+AAEAAAAaMAAF
3 XX AAAWm+AAEAAAAaMAAG
4 AAAWm+AAEAAAAaMAAH
Similarly a materialized view created with REFRESH COMPLETE can be fast refreshed (assuming the materialized view is capable of being fast refreshed, we'll learn more about this later).
drop materialized view mv ;
create materialized view mv
REFRESH COMPLETE
as select * from t
;
select key, val, rowid
from mv ;
KEY VAL ROWID
---------- ----- ------------------
1 a AAAWnBAAEAAAAaMAAA
2 b AAAWnBAAEAAAAaMAAB
3 XX AAAWnBAAEAAAAaMAAC
4 AAAWnBAAEAAAAaMAAD
execute dbms_mview.refresh( list => 'MV', method => 'F' );
select key, val, rowid
from mv ;
KEY VAL ROWID
---------- ----- ------------------
1 a AAAWnBAAEAAAAaMAAA
2 b AAAWnBAAEAAAAaMAAB
3 XX AAAWnBAAEAAAAaMAAC
4 AAAWnBAAEAAAAaMAAD
Note how none of the rowids in MV changed, indicating a fast refresh.
Cleanup
drop materialized view mv ;
drop materialized view log on t ;
update t set val = 'c' where key = 3 ;
commit ;
Materialized Views
Purging Materialized View Logs
Oracle automatically purges rows in the materialized view log when they are no longer needed. In the example below note how the log table is empty after the refresh.
create materialized view log on t ;
create materialized view mv
refresh fast
as select * from t
;
select count(*) from mlog$_t ;
COUNT(*)
----------
0
insert into t values ( 5, 'e' ) ;
commit;
select count(*) from mlog$_t ;
COUNT(*)
----------
1
execute dbms_mview.refresh( list => 'MV', method => 'F' );
select count(*) from mlog$_t ;
COUNT(*)
----------
0
DBMS_MVEW.PURGE_LOG
If a materialized view log needs to be purged manually for some reason a procedure called DBMS_MVEW.PURGE_LOG can be used.
select count(*) from mlog$_t ;
COUNT(*)
----------
0
update t set val = 'X' where key = 5 ;
commit;
select count(*) from mlog$_t ;
COUNT(*)
----------
1
execute DBMS_MVIEW.PURGE_LOG( master => 'T', num => 9999, flag => 'delete' ) ;
select count(*) from mlog$_t ;
COUNT(*)
----------
0
The "num" and "flag" parameters can be used to partially purge the log. See the PURGE_LOG manual page for further details.
Once a materialized view log has been purged any materialized views dependent on the deleted rows cannot be fast refreshed. Attempting a fast refresh will raise an error.
execute dbms_mview.refresh( list => 'MV', method => 'F' );
BEGIN dbms_mview.refresh( list => 'MV', method => 'F' ); END;
*
ERROR at line 1:
ORA-12034: materialized view log on "SCOTT"."T" younger than last refresh
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2537
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2743
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2712
ORA-06512: at line 1
Such materialized views will need to be refreshed completely.
select * from mv ;
KEY VAL
---------- -----
1 a
2 b
3 c
4
5 e
execute dbms_mview.refresh( list => 'MV', method => 'C' );
select * from mv ;
KEY VAL
---------- -----
1 a
2 b
3 c
4
5 X
Cleanup
delete from t where key = 5 ;
commit;
drop materialized view mv ;
drop materialized view log on t ;
Materialized Views
REFRESH FAST Categories
There are three ways to categorize a materialized view's ability to be fast refreshed.
1.It can never be fast refreshed.2.It can always be fast refreshed.3.It can be fast refreshed after certain kinds of changes to the base table
but not others.For the first case Oracle will raise an error if you try to create such a materialized view with its refresh method defaulted to REFRESH FAST. In the example below table T does not have a materialized view log on it. Materialized views based on T cannot therefore be fast refreshed. If we attempt to create such a materialized view we get an error.
create materialized view MV
REFRESH FAST
as select * from t2
;
as select * from t2
*
ERROR at line 3:
ORA-23413: table "SCOTT"."T2" does not have a materialized view log
For the second case materialized views are created without error, obviously, and will always be fast refreshed unless a complete refresh is explicitly requested. The third case is a little trickier. The next example demonstrates why.
select * from t2 ;
KEY T_KEY AMT
---------- ---------- ----------
10 1 100
20 1 300
30 1 200
40 2 250
50 2 150
create materialized view log on t2
with primary key, rowid, sequence ( t_key, amt )
including new values
;
create materialized view mv
REFRESH FAST
as
select t_key, max( amt ) amt_max
from t2
group by t_key
;
select rowid, t_key, amt_max from mv ;
ROWID T_KEY AMT_MAX
------------------ ---------- ----------
AAAhMzAAEAAAEG8AAA 1 300
AAAhMzAAEAAAEG8AAB 2 250
So far everything works as expected. We created a materialized view log and created a materialized view with fast refresh as its default refresh method. Let's try inserting a row into the base table.
insert into t2 values ( 5, 2, 500 );
commit;
execute dbms_mview.refresh( list => 'MV', method => 'F' );
select rowid, t_key, amt_max from mv ;
ROWID T_KEY AMT_MAX
------------------ ---------- ----------
AAAhMzAAEAAAEG8AAA 1 300
AAAhMzAAEAAAEG8AAB 2 500
Again, it worked as expected. The view was fast refreshed (the rowid's did not change after the DBMS_MVIEW.REFRESH command) and the materialized view correctly shows 500 as the maximum value for rows with T_KEY = 2. Now let's try deleting a row from the base table.
delete from t2 where key = 5 ;
commit;
execute dbms_mview.refresh( list => 'MV', method => 'F' );
BEGIN dbms_mview.refresh( list => 'MV', method => 'F' ); END;
*
ERROR at line 1:
ORA-32314: REFRESH FAST of "SCOTT"."MV" unsupported after deletes/updates
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2255
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2461
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2430
ORA-06512: at line 1
This time we received an error when we attempted a fast refresh. The reason is because this type of materialized view is an "insert-only" materialized view, i.e. it is only fast refreshable for inserts and direct loads, not updates or deletes. (We will see why it is an insert-only view in the next topic, DBMS_MVIEW.EXPLAIN_MVIEW.) To synchronize an insert-only materialized view after a delete we need to do a complete refresh.
execute dbms_mview.refresh( list => 'MV', method => 'C' );
select rowid, t_key, amt_max from mv ;
ROWID T_KEY AMT_MAX
------------------ ---------- ----------
AAAhMzAAEAAAEG8AAC 1 300
AAAhMzAAEAAAEG8AAD 2 250
Restrictions on Fast Refresh
So how do we know whether a materialized view can be fast refreshed each time, sometimes, or never? One way would be to learn all the documented restrictions for fast refreshable materialized views. Here are some of them.
In general materialized views cannot be fast refreshed if the base tables do not have materialized view logs or the defining query:
contains an analytic function
contains non-repeating expressions like SYSDATE or ROWNUM contains RAW or LONG RAW data types contains a subquery in the SELECT clause contains a MODEL clause contains a HAVING clause contains nested queries with ANY, ALL, or NOT EXISTS contains a CONNECT BY clause references remote tables in different databases references remote tables in a single database and defaults to the ON
COMMIT refresh mode references other materialized views which are not join or aggregate
materialized views.There are even more restrictions for materialized views containing joins, aggregates, UNION ALL, subqueries, etc. They are documented in various sections of a few different manuals and are too numerous and complex to repeat here. The following links can help you find them if required though.
CREATE MATERIALIZED VIEW - FAST Clause General Restrictions on Fast Refresh Restrictions on Fast Refresh on Materialized Views with Joins Only Restrictions on Fast Refresh on Materialized Views with Aggregates Restrictions on Fast Refresh on Materialized Views with UNION ALL Restrictions for Materialized Views with Subqueries Restrictions for Materialized Views with Unions Containing Subqueries Restrictions for Using Multitier Materialized Views Restrictions for Materialized Views with Collection Columns
Fortunately there is a second, simpler alternative for determining whether a materialized view is fast refreshable or not. It uses the DBMS_MVIEW.EXPLAIN_MVIEW utility which we will explore next.
Cleanup
drop materialized view mv ;
drop materialized view log on t2 ;
Materialized Views
DBMS_MVIEW.EXPLAIN_MVIEW
As we saw in the preceding topic, predicting whether or not a materialized view is fast refreshable can be complicated. TheDBMS_MVIEW.EXPLAIN_MVIEW utility can simplify this task however. Full details on how the utility works are available at the preceding link. The material below will help you use the utility effectively.
MV_CAPABILITIES_TABLE
There are two ways to get the output from DBMS_MVIEW.EXPLAIN_MVIEW, via a table or via a varray. To use the table method the current schema must contain a table called MV_CAPABILITIES_TABLE. The full, documented CREATE TABLE command for MV_CAPABILITIES_TABLE can be found on UNIX systems at $ORACLE_HOME/rdbms/admin/utlxmv.sql. It is also available in Oracle's documentation at Oracle Database Data Warehousing Guide - Basic Materialized Views - Using MV_CAPABILITIES_TABLE (see Gotchafor a related bug). Here is an abridged version.
create table MV_CAPABILITIES_TABLE
(
statement_id varchar(30) ,
mvowner varchar(30) ,
mvname varchar(30) ,
capability_name varchar(30) ,
possible character(1) ,
related_text varchar(2000) ,
related_num number ,
msgno integer ,
msgtxt varchar(2000) ,
seq number
) ;
VARRAY Output
Using DBMS_MVIEW.EXPLAIN_MVIEW with the table output method typically involves
1.deleting old rows from MV_CAPABILITIES_TABLE2.running DBMS_MVIEW.EXPLAIN_MVIEW3.selecting new rows from MV_CAPABILITIES_TABLE.
To save time in this tutorial we will use DBMS_MVIEW.EXPLAIN_MVIEW's varray output option instead and supplement it with a custom function called MY_MV_CAPABILITIES.
create or replace function my_mv_capabilities
(
p_mv in varchar2 ,
p_capability_name_filter in varchar2 default '%' ,
p_include_pct_capabilities in varchar2 default 'N' ,
p_linesize in number default 80
)
return clob
as
--------------------------------------------------------------------------------
-- From http://www.sqlsnippets.com/en/topic-12884.html
--
-- Parameters:
--
-- p_mv
-- o this value is passed to DBMS_MVIEW.EXPLAIN_MVIEW's "mv" parameter
-- o it can contain either a query, CREATE MATERIALIZED VIEW command text,
-- or a materialized view name
--
-- p_capability_name_filter
-- o use either REFRESH, REWRITE, PCT, or the default
--
-- p_include_pct_capabilities
-- Y - capabilities like REFRESH_FAST_PCT are included in the report
-- N - capabilities like REFRESH_FAST_PCT are not included in the report
--
-- p_linesize
-- o the maximum size allowed for any line in the report output
-- o data that is longer than this value will be word wrapped
--
-- Typical Usage:
--
-- set long 5000
-- select my_mv_capabilities( 'MV_NAME' ) as mv_report from dual ;
--
-- o the value 5000 is arbitraty; any value big enough to contain the
-- report output will do
--
--------------------------------------------------------------------------------
pragma autonomous_transaction ;
v_nl constant char(1) := unistr( '\000A' ); -- new line
v_previous_possible char(1) := 'X' ;
v_capabilities sys.ExplainMVArrayType ;
v_output clob ;
begin
dbms_mview.explain_mview( mv => p_mv, msg_array => v_capabilities ) ;
for v_capability in
(
select
capability_name ,
possible ,
related_text ,
msgtxt
from
table( v_capabilities )
where
capability_name like '%' || upper( p_capability_name_filter ) || '%' and
not
( capability_name like '%PCT%' and
upper(p_include_pct_capabilities) = 'N'
)
order by
mvowner ,
mvname ,
possible desc ,
seq
)
loop
------------------------------------------------------------
-- print section heading
------------------------------------------------------------
if v_capability.possible <> v_previous_possible then
v_output :=
v_output
|| v_nl
|| case v_capability.possible
when 'T' then 'Capable of: '
when 'Y' then 'Capable of: '
when 'F' then 'Not Capable of: '
when 'N' then 'Not Capable of: '
else v_capability.possible || ':'
end
|| v_nl
;
end if;
v_previous_possible := v_capability.possible ;
------------------------------------------------------------
-- print section body
------------------------------------------------------------
declare
v_indented_line_size varchar2(3) := to_char( p_linesize - 5 );
begin
-- print capability name indented 2 spaces
v_output :=
v_output
|| v_nl
|| ' '
|| v_capability.capability_name
|| v_nl
;
-- print related text indented 4 spaces and word wrapped
if v_capability.related_text is not null then
v_output :=
v_output
|| regexp_replace
( v_capability.related_text || ' '
, '(.{1,'
|| v_indented_line_size || '} |.{1,'
|| v_indented_line_size || '})'
, ' \1' || v_nl
)
;
end if;
-- print message text indented 4 spaces and word wrapped
if v_capability.msgtxt is not null then
v_output :=
v_output
|| regexp_replace
( v_capability.msgtxt || ' '
, '(.{1,'
|| v_indented_line_size || '} |.{1,'
|| v_indented_line_size || '})'
, ' \1' || v_nl
)
;
end if;
end;
end loop;
commit ;
return( v_output );
end;
/
show errors
No errors.
This completes our preparations. Now let's see DBMS_MVIEW.EXPLAIN_VIEW in action.
DBMS_MVIEW.EXPLAIN_MVIEW With a Query
DBMS_MVIEW.EXPLAIN_MVIEW can analyze three different types of materialized view code:
1.a defining query2.a CREATE MATERIALIZED VIEW command3.an existing materialized view.
Here is an example that explains a simple query which could appear as the defining query in a CREATE MATERIALIZED VIEW command.
set long 5000
select my_mv_capabilities( 'SELECT * FROM T', 'REFRESH' ) as mv_report from dual ;
MV_REPORT
--------------------------------------------------------------------------------
Capable of:
REFRESH_COMPLETE
Not Capable of:
REFRESH_FAST
REFRESH_FAST_AFTER_INSERT
SCOTT.T
the detail table does not have a materialized view log
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
(Descriptions of each capability name are available at Table 8-7 CAPABILITY_NAME Column Details. A list of messages and related text is available at Table 8-8 MV_CAPABILITIES_TABLE Column Details.)
The EXPLAIN_MVIEW output above shows that fast refresh is not possible in this case because T has no materialized view log.
Note that DBMS_MVIEW.EXPLAIN_MVIEW can report on a materialized view's refresh, rewrite, and partition change tracking (PCT) capabilities. For now we will only examine refresh capabilities. Rewrite capabilities will be covered in Query Rewrite Restrictions and Capabilities.
DBMS_MVIEW.EXPLAIN_MVIEW With CREATE MATERIALIZED VIEW
Now let's create a materialized view log on T and then use EXPLAIN_MVIEW to explain the capabilities of an entire CREATE MATERIALIZED VIEW command.
create materialized view log on t ;
select
my_mv_capabilities
( 'CREATE MATERIALIZED VIEW MV REFRESH FAST AS SELECT * FROM T'
, '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
This time we see that a materialized view using our simple query could be fast refreshable in all cases.
DBMS_MVIEW.EXPLAIN_MVIEW With Existing Materialized View
For our last example we will explain an existing materialized view, the insert-only one we saw in the preceding topic REFRESH FAST Categories.
create materialized view log on t2
with primary key, rowid, sequence ( t_key, amt )
including new values
;
create materialized view mv
refresh fast
as
select t_key, max( amt ) amt_max
from t2
group by t_key
;
select my_mv_capabilities( 'MV', 'REFRESH' ) as mv_report from dual ;
MV_REPORT
--------------------------------------------------------------------------------
Capable of:
REFRESH_COMPLETE
REFRESH_FAST
REFRESH_FAST_AFTER_INSERT
Not Capable of:
REFRESH_FAST_AFTER_ONETAB_DML
mv uses the MIN or MAX aggregate functions
REFRESH_FAST_AFTER_ONETAB_DML
COUNT(*) is not present in the select list
REFRESH_FAST_AFTER_ANY_DML
see the reason why REFRESH_FAST_AFTER_ONETAB_DML is disabled
Here we see that fast refresh is available after inserts, but not other types of DML. Note also that the "REFRESH_FAST" capability will appear whenever at least one of the other REFRESH_FAST_% capabilities is available. It does not mean the materialized view is fast refreshable in all cases.
Gotcha
Both the $ORACLE_HOME/rdbms/admin/utlxmv.sql file and the CREATE TABLE command at Oracle Database Data Warehousing Guide - Basic Materialized Views - Using MV_CAPABILITIES_TABLE state the values in MV_CAPABILITIES_TABLE.POSSIBLE will either be "T" or "F".
CREATE TABLE MV_CAPABILITIES_TABLE
...
POSSIBLE CHARACTER(1), -- T = capability is possible
-- F = capability is not possible
...
In actual use we can see the values are really "Y" and "N".
delete from mv_capabilities_table ;
execute dbms_mview.explain_mview( 'select * from t' );
commit;
column possible format a8
select distinct POSSIBLE from mv_capabilities_table ;
POSSIBLE
--------
Y
N
The values "T" and "F" are, however, used when DBMS_MVIEW.EXPLAIN_MVIEW output is saved to a varray.
Cleanup
set long 80
drop materialized view mv ;
drop materialized view log on t ;
drop materialized view log on t2 ;
Materialized Views
REFRESH FORCE
In REFRESH FAST Categories and DBMS_MVIEW.EXPLAIN_MVIEW we saw an insert-only materialized view which could be fast refreshed after inserts into the base table but needed a complete refresh after other types of DML. With these types of materialized views it is often most convenient to let Oracle decide which refresh method is best. The REFRESH FORCE method does just that. It performs a FAST refresh if possible, otherwise it performs a COMPLETE refresh.
create materialized view log on t2
with primary key, rowid, sequence ( t_key, amt )
including new values
;
create materialized view mv
REFRESH FORCE
as
select t_key, max( amt ) amt_max
from t2
group by t_key
;
select rowid, t_key, amt_max from mv ;
ROWID T_KEY AMT_MAX
------------------ ---------- ----------
AAAWpLAAEAAAAaMAAA 1 300
AAAWpLAAEAAAAaMAAB 2 250
First let's try an insert and a refresh.
insert into t2 values ( 5, 2, 500 );
commit;
execute dbms_mview.refresh( list => 'MV' );
select rowid, t_key, amt_max from mv ;
ROWID T_KEY AMT_MAX
------------------ ---------- ----------
AAAWpLAAEAAAAaMAAA 1 300
AAAWpLAAEAAAAaMAAB 2 500
Since the rowids did not change but the AMT_MAX values did we can tell that a FAST refresh was performed. Now let's try a delete followed by a refresh.
delete from t2 where key = 5 ;
commit;
execute dbms_mview.refresh( list => 'MV' );
select rowid, t_key, amt_max from mv ;
ROWID T_KEY AMT_MAX
------------------ ---------- ----------
AAAWpLAAEAAAAaMAAC 1 300
AAAWpLAAEAAAAaMAAD 2 250
In the REFRESH FAST Categories topic we received an "ORA-32314: REFRESH FAST of "SCOTT"."MV" unsupported after deletes/updates" error at this point. This time with REFRESH FORCE we did not. Instead Oracle performed a COMPLETE refresh (note how the rowids for each row changed).
Cleanup
drop materialized view mv ;
drop materialized view log on t2 ;
Materialized Views
NEVER REFRESH
If for some reason we need to prevent refresh operations of any sort, FAST or COMPLETE, on our materialized views we can use theNEVER REFRESH method.
create materialized view mv
NEVER REFRESH
as select * from t
;
select * from mv ;
KEY VAL
---------- -----
1 a
2 b
3 c
4
Let's see what happens when we update the base table and then attempt a refresh.
update t set val = upper(val) ;
commit ;
execute dbms_mview.refresh( 'MV' );
BEGIN dbms_mview.refresh( 'MV' ); END;
*
ERROR at line 1:
ORA-23538: cannot explicitly refresh a NEVER REFRESH materialized view ("MV")
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2537
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2743
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 2712
ORA-06512: at line 1
Oracle prevented the refresh by raising an error.
I cannot see a practical reason for having a materialized view with NEVER REFRESH set at all times. (If you know of any please let me know using the Comments link below.) NEVER REFRESH can come in handy though when refresh operations on a materialized view need to be prevented temporarily during maintenance or debugging operations. In this case the materialized view's refresh mode can be changed to NEVER REFRESH using the ALTER MATERIALIZED VIEW command.
Cleanup
drop materialized view mv ;
update t set val = lower(val) ;
commit ;
Materialized Views
ON DEMAND
Up to this point in the tutorial we have always refreshed our materialized views manually with the DBMS_MVIEW.REFRESH command. This is know as ON DEMAND refreshing and it is the default refresh mode when none is specified in the CREATE MATERIALIZED VIEW command. In other words this
create materialized view mv
as select * from t
;
is equivalent to this.
drop materialized view mv ;
create materialized view mv
REFRESH ON DEMAND
as select * from t
;
To refresh ON DEMAND materialized views we explicitly call one of the following procedures.
DBMS_MVIEW.REFRESH DBMS_MVIEW.REFRESH_ALL_MVIEWS DBMS_MVIEW.REFRESH_DEPENDENT
Here is an example that uses DBMS_MVIEW.REFRESH.
insert into t values ( 5, 'e' );
commit;
select * from mv where key = 5 ;
no rows selected
execute DBMS_MVIEW.REFRESH( 'MV' );
select * from mv where key = 5 ;
KEY VAL
---------- -----
5 e
Cleanup
drop materialized view mv ;
delete from t where key = 5 ;
commit;
Materialized Views
ON COMMIT
In some situations it would be convenient to have Oracle refresh a materialized view automatically whenever changes to the base table are committed. This is possible using the ON COMMIT refresh mode. Here is an example.
create materialized view log on t ;
create materialized view mv
REFRESH FAST ON COMMIT
as select * from t
;
select rowid, key, val from mv ;
ROWID KEY VAL
------------------ ---------- -----
AAAXNGAAEAAAAasAAA 1 a
AAAXNGAAEAAAAasAAB 2 b
AAAXNGAAEAAAAasAAC 3 c
AAAXNGAAEAAAAasAAD 4
Let's see what happens to the view in the course of an insert operation.
insert into t values ( 5, 'e' );
select rowid, key, val from mv ;
ROWID KEY VAL
------------------ ---------- -----
AAAXNGAAEAAAAasAAA 1 a
AAAXNGAAEAAAAasAAB 2 b
AAAXNGAAEAAAAasAAC 3 c
AAAXNGAAEAAAAasAAD 4
Nothing happend yet. Let's issue a COMMIT.
commit;
select rowid, key, val from mv ;
ROWID KEY VAL
------------------ ---------- -----
AAAXNGAAEAAAAasAAA 1 a
AAAXNGAAEAAAAasAAB 2 b
AAAXNGAAEAAAAasAAC 3 c
AAAXNGAAEAAAAasAAD 4
AAAXNGAAEAAAAatAAA 5 e
Note how the materialized view was automatically fast refreshed after the COMMIT command. No call to DBMS_MVIEW.REFRESH was required.
Restrictions
Materialized views can only refresh ON COMMIT in certain situations.
1. The materialized view cannot contain object types or Oracle-supplied types.
2. The base tables will never have any distributed transactions applied to them.
The first case produces an error during the CREATE MATERIALIZED VIEW command.
-- this materialized view is not fast refreshable
-- because the materialized view contains an Oracle-supplied type
create materialized view mv2
REFRESH FAST ON COMMIT
as select key, val, sys_xmlgen( val ) as val_xml from t
;
as select key, val, sys_xmlgen( val ) as val_xml from t
*
ERROR at line 3:
ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view
The second case generates an error when a distributed transaction is attempted on the base table. In the following example materialized view MV (created at the top of this page) was created with REFRESH FAST. Attempting a distributed transaction on its base table, T, will therefore raise an error.
insert into t select key+10, val from T@REMOTE ;
commit;
commit
*
ERROR at line 1:
ORA-02050: transaction 5.21.5632 rolled back, some remote DBs may be in-doubt
ORA-02051: another session in same transaction failed
(REMOTE is a database link which loops back to the current account.)
ON DEMAND materialized views have no such restriction, as the following snippet demonstrates.
alter materialized view mv refresh ON DEMAND ;
insert into t select key+10, val from T@REMOTE ;
commit;
select * from t ;
KEY VAL
---------- -----
1 a
2 b
3 c
4
5 e
11 a
12 b
13 c
14
15 e
-- cleanup test data in preparation for next section
delete from t where key >= 5 ;
commit ;
Gotcha
The SQL Language Reference manual says this about the ON COMMIT clause.
"Specify ON COMMIT to indicate that a fast refresh is to occur whenever the database commits a transaction that operates on a master table of the materialized view." -- Oracle® Database SQL Language Reference: CREATE MATERIALIZED VIEW
When I first read this I assumed it meant that "REFRESH COMPLETE ON COMMIT" is not allowed. I also assumed that specifying "REFRESH ON COMMIT" is equivalent to specifying "REFRESH FAST ON COMMIT". The following examples prove neither is correct however.
create materialized view mv2
REFRESH COMPLETE ON COMMIT
as select key, val from t
;
As we can see the CREATE MATERIALZIED view command succeeded even though COMPLETE, not FAST, was specified with ON COMMIT. The next example examines the behavior of "REFRESH ON COMMIT" without a specified refresh method.
drop materialized view log on t ;
-- fast refreshable materialized views on T can no longer be created on T
-- because it has no materialized view log
drop materialized view mv2 ;
create materialized view mv2
REFRESH ON COMMIT
as select key, val from t
;
select rowid, key, val from mv2 ;
ROWID KEY VAL
------------------ ---------- -----
AAAXNMAAEAAAAakAAA 1 a
AAAXNMAAEAAAAakAAB 2 b
AAAXNMAAEAAAAakAAC 3 c
AAAXNMAAEAAAAakAAD 4
insert into t values ( 5, 'e' );
commit ;
select rowid, key, val from mv2 ;
ROWID KEY VAL
------------------ ---------- -----
AAAXNMAAEAAAAakAAE 1 a
AAAXNMAAEAAAAakAAF 2 b
AAAXNMAAEAAAAakAAG 3 c
AAAXNMAAEAAAAakAAH 4
AAAXNMAAEAAAAakAAI 5 e
The fact that all the rowid's in MV2 changed after the INSERT transaction committed confirms that a complete refresh took place during the commit. "REFRESH ON COMMIT" is not therefore equivalent to "REFRESH FAST ON COMMIT". In fact, when no REFRESH method is specified the default behaviour is "REFRESH FORCE" regardless of whether ON COMMIT is used or not.
Given these observations I can only conclude the documentation is either in error or misleading when it says "specify ON COMMIT to indicate that a fast refresh is to occur".
Cleanup
drop materialized view mv ;
drop materialized view mv2 ;
delete from t where key >= 5 ;
commit ;
Materialized Views
Constraints
System Generated Constraints
When a materialized view is created Oracle may add system generated constraints to its underlying table (i.e. the table containing the results of the query, not to be confused with a base table). In the following example note how Oracle automatically adds a primary key constraint to the table called "MV", which is part of the materialized view also called "MV".
create materialized view mv
as select key, val from t
;
column constraint_name format a20
column constraint_type format a15
column index_name format a15
select constraint_name, constraint_type, index_name
from user_constraints
where TABLE_NAME = 'MV' ;
CONSTRAINT_NAME CONSTRAINT_TYPE INDEX_NAME
-------------------- --------------- ---------------
SYS_C0019948 P SYS_C0019948
In the next example Oracle automatically adds a check constraint.
drop materialized view mv ;
describe t2
Name Null? Type
-------------------------------------------- -------- ------------------------------
KEY NOT NULL NUMBER
T_KEY NOT NULL NUMBER
AMT NOT NULL NUMBER
create materialized view log on t2
with primary key, rowid, sequence ( t_key, amt )
including new values
;
create materialized view mv
refresh fast on commit
as
select t_key, count(*) row_count
from t2
group by t_key
;
column search_condition format a30
select constraint_name, constraint_type, search_condition
from user_constraints
where table_name = 'MV' ;
CONSTRAINT_NAME CONSTRAINT_TYPE SEARCH_CONDITION
-------------------- --------------- ------------------------------
SYS_C0019949 C "T_KEY" IS NOT NULL
Adding Your Own Constraints
If necessary we can create our own constraints on materialized view tables in addition to the ones Oracle may add. When the materialized view is in ON COMMIT mode these constraints effectively constrain the materialized view's base tables. Let's see this in action by creating a check constraint on MV.
select * from t2 ;
KEY T_KEY AMT
---------- ---------- ----------
10 1 100
20 1 300
30 1 200
40 2 250
50 2 150
alter table mv -- note we used "alter table" here
add CONSTRAINT MY_CONSTRAINT CHECK ( ROW_COUNT <= 3 ) DEFERRABLE
;
select constraint_name, constraint_type, search_condition
from user_constraints
where table_name = 'MV' ;
CONSTRAINT_NAME CONSTRAINT_TYPE SEARCH_CONDITION
-------------------- --------------- ------------------------------
SYS_C0019949 C "T_KEY" IS NOT NULL
MY_CONSTRAINT C ROW_COUNT <= 3
Now any attempt to create more than 3 rows per group in table T2 will generate an error at commit time.
insert into T2 values ( 5, 1, 500 );
commit;
commit
*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (SCOTT.MY_CONSTRAINT) violated
Implementing multirow validation rules such as this one properly is not possible using check constraints on regular tables. Implementing them using triggers can be difficult if not impossible. With materialized views they are declared using a few lines of code and are virtually bullet proof when applied correctly. We will learn more about this powerful multirow validation approach in a future SQL Snippets tutorial so stay tuned! In the mean time Ask Tom "Declarative Integrity" has some good information on the subject.
Gotcha
When we created MY_CONSTRAINT above we use an ALTER TABLE command. Curiously enough an ALTER MATERIALIZED VIEW command would have worked too.
ALTER MATERIALIZED VIEW mv
add constraint my_second_constraint check ( row_count < 4 ) deferrable
;
select constraint_name, constraint_type, search_condition
from user_constraints
where table_name = 'MV' ;
CONSTRAINT_NAME CONSTRAINT_TYPE SEARCH_CONDITION
-------------------- --------------- ------------------------------
SYS_C0019949 C "T_KEY" IS NOT NULL
MY_CONSTRAINT C ROW_COUNT <= 3
MY_SECOND_CONSTRAINT C row_count < 4
The Oracle manual page for ALTER MATERIALIZED VIEW however does not indicate that constraints can be added this way. Until the documentation says this is legal it is best to use ALTER TABLE.
Cleanup
drop materialized view mv ;
drop materialized view log on t2 ;
Materialized Views
Indexes
When a materialized view is created Oracle may add system generated indexes to its underlying table (i.e. the table containing the results of the query, not to be confused with a base table). In the following example note how Oracle automatically adds an index to implement the system generated primary key we saw in the preceding topic, Constraints.
create materialized view mv
as select key, val from t
;
column index_name format a15
column column_name format a15
select
index_name ,
i.uniqueness ,
ic.column_name
from
user_indexes i
inner join user_ind_columns ic
using ( index_name )
where
i.table_name = 'MV'
;
INDEX_NAME UNIQUENES COLUMN_NAME
--------------- --------- ---------------
SYS_C0019959 UNIQUE KEY
In the next example Oracle automatically generates a function based index.
drop materialized view mv ;
create materialized view log on t2
with primary key, rowid, sequence ( t_key, amt )
including new values
;
create materialized view mv
refresh fast on commit
as
select t_key, COUNT(*) ROW_COUNT
from t2
group by t_key
;
column column_expression format a35
select
index_name ,
i.uniqueness ,
ic.column_name ,
ie.column_expression
from
user_indexes i
inner join user_ind_columns ic
left outer join user_ind_expressions ie
using ( index_name )
using ( index_name )
where
ic.table_name = 'MV'
;
INDEX_NAME UNIQUENES COLUMN_NAME COLUMN_EXPRESSION
--------------- --------- --------------- -----------------------------------
I_SNAP$_MV UNIQUE SYS_NC00003$ SYS_OP_MAP_NONNULL("T_KEY")
(Note that SYS_OP_MAP_NONNULL is an undocumented Oracle function. Do not attempt to use it in your own code. See Nulls and Equality: SQL Only for additional info.)
Adding Your Own Indexes
We can add out own indexes to MV just as we would a regular table. In the following example we will add an index on the T_KEY column.
create index MY_INDEX on mv ( T_KEY ) ;
select
index_name ,
i.uniqueness ,
ic.column_name
from
user_indexes i
inner join user_ind_columns ic
using ( index_name )
where
i.table_name = 'MV'
;
INDEX_NAME UNIQUENES COLUMN_NAME
--------------- --------- ---------------
I_SNAP$_MV UNIQUE SYS_NC00003$
MY_INDEX NONUNIQUE T_KEY
To confirm that Oracle uses our index in queries let's turn SQL*Plus's Autotrace feature on and execute a query.
set autotrace on explain
set linesize 95
select *
from mv
where t_key = 2 ;
T_KEY ROW_COUNT
---------- ----------
2 2
Execution Plan
----------------------------------------------------------
Plan hash value: 2793437614
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 2 (0)| 00:00:01 |
| 1 | MAT_VIEW ACCESS BY INDEX ROWID| MV | 1 | 26 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | MY_INDEX | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("T_KEY"=2)
Note
-----
- dynamic sampling used for this statement
Note how the optimizer chose an INDEX RANGE SCAN from MY_INDEX in step 2.
Cleanup
drop materialized view mv ;
drop materialized view log on t2 ;
Materialized Views
ENABLE QUERY REWRITE
Materialized views can be useful for pre-calculating and storing derived values such as AMT_MAX in the following snippet.
create materialized view log on t2
with primary key, rowid, sequence ( t_key, amt )
including new values
;
create materialized view mv
refresh fast on commit
as
select t_key, MAX( AMT ) AMT_MAX
from t2
group by t_key
;
Such materialized views make queries like this
select t_key, amt_max
FROM MV
order by t_key ;
T_KEY AMT_MAX
---------- ----------
1 300
2 250
faster than its equivalent query.
select t_key, max( amt ) as amt_max
FROM T2
group by t_key
order by t_key ;
T_KEY AMT_MAX
---------- ----------
1 300
2 250
Wouldn't it be nice if Oracle could use the information in MV to resolve this last query too? If your database has a feature called Query Rewrite available and enabled this happens automatically. To see it in action we first need to make the materialized view available to Query Rewrite like this.
alter materialized view mv ENABLE QUERY REWRITE ;
(See Gotcha - ORA-00439 below if you encounter an ORA-00439 error at this step.)
Note that materialized views which do not include the ENABLE QUERY REWRITE clause will have Query Rewrite disabled by default.
Next we collect statistics on the materialized view to help Oracle optimize the query rewrite process.
execute dbms_stats.gather_table_stats( user, 'MV' ) ;
Finally we can confirm Oracle will use the materialized view in queries by turning SQL*Plus's Autotrace feature on.
set autotrace on explain
set linesize 95
select t_key, max( amt ) as amt_max
FROM T2
group by t_key
order by t_key ;
T_KEY AMT_MAX
---------- ----------
1 300
2 250
Execution Plan
----------------------------------------------------------
Plan hash value: 446852971
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 14 | 4 (25)| 00:00:01 |
| 1 | SORT ORDER BY | | 2 | 14 | 4 (25)| 00:00:01 |
| 2 | MAT_VIEW REWRITE ACCESS FULL| MV | 2 | 14 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Note how the optimizer chose to access MV for its pre-calculated MAX(AMT) values in line 2 even though the query itself made no mention of MV. Without the Query Rewrite feature the execution plan would look like this.
alter session set QUERY_REWRITE_ENABLED = FALSE ;
select t_key, max( amt ) as amt_max
FROM T2
group by t_key
order by t_key ;
T_KEY AMT_MAX
---------- ----------
1 300
2 250
Execution Plan
----------------------------------------------------------
Plan hash value: 50962384
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 130 | 4 (25)| 00:00:01 |
| 1 | SORT GROUP BY | | 5 | 130 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| T2 | 5 | 130 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement
Note how the optimizer chose to access T2 this time. Each time this query is executed it has to re-calculate MAX(VAL) from the information in T2 for each group, a more expensive approach than simply selecting pre-calculated column values from MV is.
Gotcha - ORA-00439
The materialized view query rewrite feature is not available in Oracle XE and some other Oracle configurations. If you attempt to use ENABLE QUERY REWITE in an Oracle database where the feature is not enabled you will receive an ORA-00439 error.
create materialized view mv2
refresh fast on commit
ENABLE QUERY REWRITE
as
select
t_key ,
count(*) as row_count ,
count(amt) as amt_count
from t2
group by t_key
;
from t2
*
ERROR at line 9:
ORA-00439: feature not enabled: Materialized view rewrite
Cleanup
alter session set query_rewrite_enabled = true ;
set autotrace off
drop materialized view mv ;
drop materialized view log on t2 ;
Materialized Views
Query Rewrite Restrictions and Capabilities
Restrictions
Materialized views with the following characteristics cannot have query rewrite enabled:
the defining query references functions which are not DETERMINISTIC an expression in the defining query is not repeatable; e.g. an expression
containing the USER pseudo column or the SYSTIMESTAMP function.Attempting to violate these restrictions results in an error.
create materialized view mv
ENABLE QUERY REWRITE
as select key, val, USER from t
;
as select key, val, USER from t
*
ERROR at line 3:
ORA-30353: expression not supported for query rewrite
Capabilities
A few different materialized view query rewrite capabilities exist. In EXPLAIN_MVIEW we used a utility called MY_MV_CAPABILITIES to explore a materialized view's refresh capabilities. In the snippets below we will use this same utility to explore rewrite capabilities.
First lets look at a simple, single table materialized view with query rewrite disabled.
create materialized view mv
DISABLE QUERY REWRITE
as select key, val from t
;
set long 5000
select my_mv_capabilities( 'MV', 'REWRITE' ) as mv_report from dual ;
MV_REPORT
--------------------------------------------------------------------------------
Not Capable of:
REWRITE
REWRITE_FULL_TEXT_MATCH
query rewrite is disabled on the materialized view
REWRITE_PARTIAL_TEXT_MATCH
query rewrite is disabled on the materialized view
REWRITE_GENERAL
query rewrite is disabled on the materialized view
This materialized view obviously has no rewrite capabilities available to it. (Descriptions of each capability name are available at Table 8-7 CAPABILITY_NAME Column Details.)
Enabling query rewrite on the materialized view changes this.
alter materialized view mv ENABLE QUERY REWRITE ;
select my_mv_capabilities( 'MV', 'REWRITE' ) as mv_report from dual ;
MV_REPORT
--------------------------------------------------------------------------------
Capable of:
REWRITE
REWRITE_FULL_TEXT_MATCH
REWRITE_PARTIAL_TEXT_MATCH
REWRITE_GENERAL
Now all rewrite capabilities are available. If the materialized view happened to referenced a remote table then some rewrite capabilities would be available, but not others.
drop materialized view mv ;
create materialized view mv
enable query rewrite
as select key, val from T@REMOTE
;
select my_mv_capabilities( 'MV', 'REWRITE' ) as mv_report from dual ;
MV_REPORT
--------------------------------------------------------------------------------
Capable of:
REWRITE
REWRITE_PARTIAL_TEXT_MATCH
REWRITE_GENERAL
Not Capable of:
REWRITE_FULL_TEXT_MATCH
T
mv references a remote table or view in the FROM list
Cleanup
drop materialized view mv ;