The LIKE-clause can ignore indexes, causing queries to run forever while doing full table scans. This document describes how to tune such SQL statements by making use of Oracle Text or reverse key indexes.
Tuning the ‘LIKE’ Clause:
Generally, search arguments in the WHERE clause such as “IS NULL”, “<>”, “!=”, “!>”, “!<”, “NOT”, “NOT EXISTS”, “NOT IN”, “NOT LIKE”, and “LIKE ‘%500′” prevents Oracle from using an index to perform the search (however, not always).
If you use LIKE in your WHERE clause, try to specify one or more leading characters if at all possible. For example, use LIKE ‘m%’ and not LIKE ‘%m’. If you specify a leading character, Oracle has a better chance of being able to use an index to perform the query – this will increase performance and reduce the load on the database server.
To avoid such full table scans, consider the following scenarios:
Case 1: Tuning the LIKE-clause by using Oracle Text indexes
Requirements:
A. Install and configure Oracle’s TEXT (done as part of the installation process).
B. Check whether Oracle TEXT is installed by looking for the ‘CTXSYS’ schema.
The problem
I. Create a test table:
create table t as select * from tab;
CREATE INDEX normal_index ON t
(TNAME) NOPARALLEL;
SQL> select TNAME from t where TNAME LIKE '%SEG%';
TNAME
------------------------------
DBA_AUTO_SEGADV_CTL
DBA_AUTO_SEGADV_SUMMARY
DBA_HIST_SEG_STAT
DBA_HIST_SEG_STAT_OBJ
DBA_ROLLBACK_SEGS
DBA_SEGMENTS
DBA_SEGMENTS_OLD
GV_$SEGMENT_STATISTICS
GV_$SEGSTAT
GV_$SEGSTAT_NAME
GV_$SORT_SEGMENT
SEG$
SYSSEGOBJ
SYS_DBA_SEGS
SYS_USER_SEGS
USER_SEGMENTS
V_$SEGMENT_STATISTICS
V_$SEGSTAT
V_$SEGSTAT_NAME
V_$SORT_SEGMENT
WRH$_SEG_STAT
WRH$_SEG_STAT_BL
WRH$_SEG_STAT_OBJ
WRI$_SEGADV_CNTRLTAB
WRI$_SEGADV_OBJLIST
II. Run a query to demonstrate that it will do a full table scan:
SQL> select TNAME from t where TNAME LIKE '%SEG%';
Execution Plan
----------------------------------------------------------
Plan hash value: 2051864513
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 425 | 5 (0)| 00:00
|* 1 | INDEX FAST FULL SCAN| NORMAL_INDEX | 25 | 425 | 5 (0)| 00:00
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("TNAME" LIKE '%SEG%')
Note
-----
- dynamic sampling used for this statement
III. Drop the index:
SQL> drop index normal_index;
Index dropped.
Solution:
I. Create an Oracle Text index on the columns that you would like to search:
SQL> create index xyz_oracle_txt_idx on t(TNAME) INDEXTYPE IS CTXSYS.CONTEXT;
Index created.
II. Change the LIKE-clause to an CONTAINS-clause – WHERE CONTAINS(TNAME, ‘%SEG%’) > 0;
SQL> set autotrace off
SQL> select TNAME from t WHERE CONTAINS(TNAME, '%SEG%')>0;
TNAME
------------------------------
SEG$
V_$SORT_SEGMENT
V_$SEGMENT_STATISTICS
V_$SEGSTAT_NAME
V_$SEGSTAT
GV_$SORT_SEGMENT
GV_$SEGMENT_STATISTICS
GV_$SEGSTAT_NAME
GV_$SEGSTAT
DBA_ROLLBACK_SEGS
SYSSEGOBJ
TNAME
------------------------------
SYS_USER_SEGS
SYS_DBA_SEGS
USER_SEGMENTS
DBA_SEGMENTS
DBA_SEGMENTS_OLD
WRI$_SEGADV_OBJLIST
WRI$_SEGADV_CNTRLTAB
DBA_AUTO_SEGADV_CTL
DBA_AUTO_SEGADV_SUMMARY
WRH$_SEG_STAT
WRH$_SEG_STAT_BL
TNAME
------------------------------
WRH$_SEG_STAT_OBJ
DBA_HIST_SEG_STAT
DBA_HIST_SEG_STAT_OBJ
III. Look at the SQL plan:
SQL> set autotrace traceonly explain
SQL> select TNAME from t WHERE CONTAINS(TNAME, '%SEG%')>0;
Execution Plan
----------------------------------------------------------
Plan hash value: 3734532763
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 58 | 4
| 1 | TABLE ACCESS BY INDEX ROWID| T | 2 | 58 | 4
|* 2 | DOMAIN INDEX | XYZ_ORACLE_TXT_IDX | | | 4
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("CTXSYS"."CONTAINS"("TNAME",'%SEG%')>0)
Note
-----
- dynamic sampling used for this statement
Better!
Case 2: Tuning the LIKE-clause by using reverse key indexes
Another trick for indexing queries with a leading wildcard character (like ‘%SON’) is to create a REVERSE index – and then programmatically reverse the LIKE-clause to read “LIKE ‘NOS%’”, effectively indexing on the other side of the text, clumsy, yet effective.
Steps:
1. Create reverse key index on columns that will be searched. For example, create a reverse key index on Cust_Name of the customer table:
CREATE INDEX Cust_Name_reverese_idx
ON customer(Cust_Name)
REVERSE;
2. Programmatically reverse the SQL LIKE-clause to read ’saliV%’:
Existing Query:
SELECT *
FROM customer
WHERE Cust_Name LIKE '%Vilas'
New Query:
SELECT *
FROM customer
WHERE Cust_Name LIKE 'saliV%';
I modified a bit after reading http://www.orafaq.com/node/1918