By Franck Pachot

.
When I passed the OCM 11g exam, I had to prepare the ‘Manage Materialized Views to improve rewrite’ topic. Now that I’m preparing the 12c upgrade I checked quickly what I’ve prepared… and it doesn’t work. This post is about query rewrite, a bug in 12c, how to understand why it doesn’t rewrite, and how rewrite is accepted when constraints are not enforced and when materialized view is stale.

SCOTT

I start with the SCOTT schema where I alter EMP to define DEPTNO as not null, meaning that I’ve a one-to-many association.


SQL> host sqlplus / as sysdba @ ?/rdbms/admin/utlsampl
SQL> alter table SCOTT.EMP modify ( DEPTNO not null);
Table altered.

And I’ll need a few additional grants for my test.


SQL> grant create materialized view, select any dictionary to scott;
Grant succeeded.

Create materialized view

I’m creating a materialized view that join the two tables DEPT and EMP


SQL> connect scott/tiger
Connected.
SQL> 
SQL> create materialized view TEST_MV enable query rewrite
  2   as
  3   select * from DEPT join EMP using (DEPTNO);
Materialized view created.

I check that query_rewrite_integrity is enabled


SQL> show parameter rewrite
 
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
query_rewrite_enabled                string      TRUE
query_rewrite_integrity              string      enforced

and run a query on EMP. The query should be able to read the materialized view instead of the table, because it has all rows and all columns. Because the cost may not be better, I force the query rewrite with the REWRITE_OR_ERROR hint: force the rewrite and fail if not possible.


SQL> set autotrace on explain
SQL> select /*+ rewrite_or_error */ * from EMP;
select /*+ rewrite_or_error */ * from EMP
                                      *
ERROR at line 1:
ORA-30393: a query block in the statement did not rewrite
 
SQL> set autotrace off

Hummm. No rewrite. If you try it on 11.2.0.3 query rewrite occurs. But not in 11.2.0.4 not in 12.1

Explain rewrite

When you have to understand why query rewrite do not occur, you can have more information with dbms_mview.explain_rewrite. In 11g we had the possibility to do it easily with dbconsole, but in 12c there is nothing in EM Express nor in SQL Developer. If I’m wrong, please tell me.

Thus, we have to use it in command line. First, we need to create the EXPLAIN_REWRITE table to store the result.


SQL> @ ?/rdbms/admin/utlxrw.sql


SQL> Rem
SQL> Rem $Header: rdbms/admin/utlxrw.sql /main/7 2011/05/20 12:21:56 traney Exp $
SQL> Rem
SQL> Rem utlxrw.sql
SQL> Rem
SQL> Rem Copyright (c) 2000, 2011, Oracle and/or its affiliates.
SQL> Rem All rights reserved.
SQL> Rem
SQL> Rem    NAME
SQL> Rem      utlxrw.sql - Create the output table for EXPLAIN_REWRITE
SQL> Rem
SQL> Rem    DESCRIPTION
SQL> Rem     Outputs of the EXPLAIN_REWRITE goes into the table created
SQL> Rem     by utlxrw.sql (called REWRITE_TABLE). So utlxrw must be
SQL> Rem     invoked before any EXPLAIN_REWRITE tests.
SQL> Rem
SQL> Rem    NOTES
SQL> Rem      If user specifies a different name in EXPLAIN_REWRITE, then
SQL> Rem      it should have been already created before calling EXPLAIN_REWRITE.
SQL> Rem
SQL> Rem    MODIFIED   (MM/DD/YY)
SQL> Rem    traney	04/05/11 - 35209: long identifiers dictionary upgrade
SQL> Rem    desingh	10/19/05 - bug#4401918:change join_back cols lengths
SQL> Rem    mthiyaga	04/29/05 - Remove unncessary comment
SQL> Rem    mthiyaga	06/08/04 - Add rewritten_txt field
SQL> Rem    mthiyaga	10/10/02 - Add extra columns
SQL> Rem    mthiyaga	09/27/00 - Create EXPLAIN_REWRITE output table
SQL> Rem    mthiyaga	09/27/00 - Created
SQL> Rem
SQL> Rem
SQL> CREATE TABLE REWRITE_TABLE(
  2  		       statement_id	     VARCHAR2(30),  -- id for the query
  3  		       mv_owner 	     VARCHAR2(128),  -- owner of the MV
  4  		       mv_name		     VARCHAR2(128),  -- name of the MV
  5  		       sequence 	     INTEGER,	    -- sequence no of the error msg
  6  		       query		     VARCHAR2(4000),-- user query
  7  		       query_block_no	     INTEGER,	    -- block no of the current subquery
  8  		       rewritten_txt	     VARCHAR2(4000),-- rewritten query
  9  		       message		     VARCHAR2(512), -- EXPLAIN_REWRITE error msg
 10  		       pass		     VARCHAR2(3),   -- rewrite pass no
 11  		       mv_in_msg	     VARCHAR2(128),  -- MV in current message
 12  		       measure_in_msg	     VARCHAR2(30),  -- Measure in current message
 13  		       join_back_tbl	    VARCHAR2(4000),-- Join back table in current msg
 14  		       join_back_col	    VARCHAR2(4000),-- Join back column in current msg
 15  		       original_cost	     INTEGER,	    -- Cost of original query
 16  		       rewritten_cost	     INTEGER,	    -- Cost of rewritten query
 17  		       flags		     INTEGER,	    -- associated flags
 18  		       reserved1	     INTEGER,	    -- currently not used
 19  		       reserved2	     VARCHAR2(10))  -- currently not used
 20  /
 
Table created.

and call the explain_rewrite procedure with the statement, and to materialized view:


SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
 
PL/SQL procedure successfully completed.

and check the result:


SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;
 
  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01150: query did not rewrite
         2 QSM-01219: no suitable materialized view found to rewrite th
           is query

But there is no reason there. It’s like the materialized view cannot even be considered for query rewrite. Well, I’ll not go into the detail here. I’ve opened bug 17651484 for that. There is a problem since 11.2.0.4 where query uses ANSI join. I like ANSI join syntax, but here I have to avoid them.

Without ANSI join


SQL> drop materialized view TEST_MV;
Materialized view dropped.

SQL> create materialized view TEST_MV enable query rewrite
  2   as
  3   select DNAME,EMP.* from DEPT , EMP where DEPT.DEPTNO=EMP.DEPTNO;

Materialized view created.

and now check rewrite:


SQL> set autotrace on explain
SQL> select /*+ rewrite_or_error */ * from EMP;
 
     EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
      7369 SMITH      CLERK           7902 17-DEC-80        800                    20
      7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30
      7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30
      7566 JONES      MANAGER         7839 02-APR-81       2975                    20
      7654 MARTIN     SALESMAN        7698 28-SEP-81       1250       1400         30
      7698 BLAKE      MANAGER         7839 01-MAY-81       2850                    30
      7782 CLARK      MANAGER         7839 09-JUN-81       2450                    10
      7788 SCOTT      ANALYST         7566 19-APR-87       3000                    20
      7839 KING       PRESIDENT            17-NOV-81       5000                    10
      7844 TURNER     SALESMAN        7698 08-SEP-81       1500          0         30
      7876 ADAMS      CLERK           7788 23-MAY-87       1100                    20
      7900 JAMES      CLERK           7698 03-DEC-81        950                    30
      7902 FORD       ANALYST         7566 03-DEC-81       3000                    20
      7934 MILLER     CLERK           7782 23-JAN-82       1300                    10

14 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 1627509066

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |    14 |   546 |     3   (0)| 00:00:01 |
|   1 |  MAT_VIEW REWRITE ACCESS FULL| TEST_MV |    14 |   546 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

SQL> set autotrace off

Good. Query rewrite occured. Here is the output from explain rewrite:


SQL> delete from REWRITE_TABLE;
2 rows deleted.
 
SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
PL/SQL procedure successfully completed.

SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;

  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01151: query was rewritten
         2 QSM-01033: query rewritten with materialized view, TEST_MV

From now, I’ll use only explain_rewrite to check different variations.

Disabled constraint

Constraints are not only there to verify integrity constraints, but also to give information to the optimizer. I’ve added the not null before because using the join instead of the table is possible only when we know that all rows from EMP have a matching row in DEPT. Now let’s see what happens if I remove the constraint:


SQL> alter table EMP disable constraint FK_DEPTNO;
Table altered.

SQL> delete from REWRITE_TABLE;
2 rows deleted.

SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
PL/SQL procedure successfully completed.

SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;

  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01150: query did not rewrite
         2 QSM-01110: query rewrite not possible with materialized view
            TEST_MV because it contains a join between tables (EMP and
           DEPT) that is not present in the query and that potentially
           eliminates rows needed by the query
         3 QSM-01052: referential integrity constraint on table, EMP, n
           ot VALID in ENFORCED integrity mode

The explain rewrite tells me everything. The join may eliminate rows, and the disabled constraint cannot ensure that no rows are eliminated.

RELY constraint

If you know that your data verifies the constraint, and don’t want the overhead to validate it, you can alter the constraint to RELY so that the optimizer can rely on it.


SQL> alter table EMP modify constraint FK_DEPTNO rely;
Table altered.

SQL> delete from REWRITE_TABLE;
3 rows deleted.

SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
PL/SQL procedure successfully completed.

SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;

  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01150: query did not rewrite
         2 QSM-01110: query rewrite not possible with materialized view
            TEST_MV because it contains a join between tables (EMP and
           DEPT) that is not present in the query and that potentially
           eliminates rows needed by the query
         3 QSM-01052: referential integrity constraint on table, EMP, n
           ot VALID in ENFORCED integrity mode

But that’s not enough. We have to read the last message. It’s not sufficient to have the constraint in RELY, we have to be trusted by the query rewrite.


SQL> alter session set query_rewrite_integrity=trusted;
Session altered.

SQL> delete from REWRITE_TABLE;
3 rows deleted.

SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
PL/SQL procedure successfully completed.

SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;

  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01151: query was rewritten
         2 QSM-01033: query rewritten with materialized view, TEST_MV

Good. Now query rewrite occurs because I told the optimizer that it can RELY on the foreign key for the current data, and I told the query rewrite that it can trust me.

STALE

Ok, but my constraint is still disabled. New DML will not be verified. The RELY concerns only current data. So what happens if I insert a row in EMP that has no matching DEPT?


SQL> insert into EMP (EMPNO,ENAME,DEPTNO) values (9999,'rebel',50);
1 row created.
 
SQL> commit;
Commit complete.

SQL> delete from REWRITE_TABLE;
2 rows deleted.

SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
PL/SQL procedure successfully completed.

SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;

  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01150: query did not rewrite
         2 QSM-01031: materialized view, TEST_MV, is stale in TRUSTED i
           ntegrity mode

Because changes have been done, the materialized view is stale. We need to lower the query rewrite security level if we still want query rewrite:


SQL> alter session set query_rewrite_integrity=stale_tolerated;
Session altered.

SQL> delete from REWRITE_TABLE;
2 rows deleted.

SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
PL/SQL procedure successfully completed.

SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;

  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01151: query was rewritten
         2 QSM-01033: query rewritten with materialized view, TEST_MV

But then the select with query rewrite will not show all rows, because the row I’ve inserted into EMP is not in the join result.

ENFORCED

So trusted may show wrong results when the constraint is not enabled. You need to enforce query rewrite integrity to avoid that.


SQL> alter session set query_rewrite_integrity=enforced;
Session altered.

SQL> delete from REWRITE_TABLE;
2 rows deleted.

SQL> exec dbms_mview.explain_rewrite('select * from EMP','TEST_MV');
PL/SQL procedure successfully completed.

SQL> select sequence,message from REWRITE_TABLE  order by statement_id,sequence;

  SEQUENCE MESSAGE
---------- ------------------------------------------------------------
         1 QSM-01150: query did not rewrite
         2 QSM-01110: query rewrite not possible with materialized view
            TEST_MV because it contains a join between tables (EMP and
           DEPT) that is not present in the query and that potentially
           eliminates rows needed by the query
         3 QSM-01052: referential integrity constraint on table, EMP, n
           ot VALID in ENFORCED integrity mode

In this case, query rewrite do not occur because the foreign key is not enforced by oracle (not enabled).

Conclusion

Query rewrite depends on referential integrity. When query_rewrite_integrity=enforced, the referential integrity must be enforced by oracle (constraint is validated and enabled). When query_rewrite_integrity=trusted, you are responsible about data integrity, and control whether rewrite can rely on your constraint, and even tolerate stale materialized view.

Don’t forget to call dbms_mview.explain_rewrite after creating the rewrite_table with utlxrw.sql