One cool technique that the optimizer can employ is the BITMAP CONVERSION TO ROWIDS method to take advantage of B-tree indexes in a means that we would normally associate with a bitmap index. This can be particularly useful with multiple predicates on individually indexed columns because it lets us establish the rows of interest before having to visit the heap blocks. Here’s an example of that in action, even when the indexes in question are Text indexes.
Enterprise Edition plan
SQL> create table MY_TAB ( col1 varchar2(50), col2 varchar2(50));
Table created.
SQL>
SQL> insert into MY_TAB
2 select dbms_random.string('x', 30), dbms_random.string('x', 30)
3 from dual
4 connect by level <= 100000;
100000 rows created.
SQL>
SQL> commit;
Commit complete.
SQL>
SQL> create index MY_TAB_IX1 on MY_TAB(col1) indextype is ctxsys.context;
Index created.
SQL> create index MY_TAB_IX2 on MY_TAB(col2) indextype is ctxsys.context;
Index created.
SQL>
SQL> exec dbms_stats.gather_table_stats('','MY_TAB');
PL/SQL procedure successfully completed.
SQL> set autotrace traceonly explain
SQL> select * from MY_TAB where contains(col1, 'string1') > 0 or contains(col2, 'string2') > 0;
Execution Plan
----------------------------------------------------------
Plan hash value: 4174159475
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 62 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| MY_TAB | 1 | 62 | 2 (0)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP OR | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 5 | SORT ORDER BY | | | | | |
|* 6 | DOMAIN INDEX | MY_TAB_IX1 | | | 1 (0)| 00:00:01 |
| 7 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 8 | SORT ORDER BY | | | | | |
|* 9 | DOMAIN INDEX | MY_TAB_IX2 | | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("CTXSYS"."CONTAINS"("COL1",'string1')>0)
9 - access("CTXSYS"."CONTAINS"("COL2",'string2')>0)
But a quick trip to the licensing guide states the following about the optimizer in Standard Edition:
The following methods are not available in SE:
Bitmapped index, bitmapped join index, and bitmap plan conversions
which make sense given that you do not have access to bitmap indexes in Standard Edition.
But all is not lost. Even though the bitmap conversion optimization is not available, the optimizer can still come up with alternate and intelligent mechanisms to extract the required rows. Whereas you might think that an OR condition on two different columns might force a full table scan, here is the same example as above in Standard Edition.
Standard Edition plan
SQL> set autotrace traceonly explain
SQL> select * from MY_TAB where contains(col1, 'string1') > 0 or contains(col2, 'string2') > 0;
Execution Plan
----------------------------------------------------------
Plan hash value: 1568130183
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 108 | 2 (0)| 00:00:01 |
| 1 | VIEW | VW_ORE_A5827389 | 2 | 108 | 2 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID| MY_TAB | 1 | 62 | 1 (0)| 00:00:01 |
|* 4 | DOMAIN INDEX | MY_TAB_IX1 | | | 1 (0)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID| MY_TAB | 1 | 62 | 1 (0)| 00:00:01 |
|* 6 | DOMAIN INDEX | MY_TAB_IX2 | | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("CTXSYS"."CONTAINS"("COL1",'string1')>0)
5 - filter(LNNVL("CTXSYS"."CONTAINS"("COL1",'string1')>0))
6 - access("CTXSYS"."CONTAINS"("COL2",'string2')>0)
SQL> set autotrace off
A “UNION ALL” suggests the two separate table access paths will potentially return duplicate rows. But notice the nice touch on line 5 – when probing the rows via MY_TAB_IDX2, the optimizer threw in an additional FILTER (LNNVL(“CTXSYS”.”CONTAINS”(“COL1”,’string1′)>0)) to remove those rows that will be returned by the the “partnering” half of the UNION ALL. In this way, we avoided a potentially expensive sort to remove duplicated results.
So you might see the occasional difference between optimizer plans for Enterprise edition versus Standard edition – but both should do a fine job at executing your queries
(Both tests done on 12.2)