<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
		>
<channel>
	<title>Comments on: Repeat After Me: NULL Values are Not Stored in Indexes?</title>
	<atom:link href="http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/feed/" rel="self" type="application/rss+xml" />
	<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/</link>
	<description>Miscellaneous Random Oracle Topics: Stop, Think, ... Understand</description>
	<lastBuildDate>Mon, 13 May 2013 14:10:06 +0000</lastBuildDate>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
	<item>
		<title>By: Yes, yes you can index nulls &#187; SQLfail</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4960</link>
		<dc:creator><![CDATA[Yes, yes you can index nulls &#187; SQLfail]]></dc:creator>
		<pubDate>Wed, 03 Oct 2012 07:11:08 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4960</guid>
		<description><![CDATA[[...] by posts from Charles Hooper and Richard Foote. Share this:FacebookTwitter    &#160;Posted by Chris Saxon at 08:00 &#160;Tagged [...]]]></description>
		<content:encoded><![CDATA[<p>[...] by posts from Charles Hooper and Richard Foote. Share this:FacebookTwitter    &nbsp;Posted by Chris Saxon at 08:00 &nbsp;Tagged [...]</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: jcon.no: Oracle Blog - Myth: NULL values are never stored in indexes</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4737</link>
		<dc:creator><![CDATA[jcon.no: Oracle Blog - Myth: NULL values are never stored in indexes]]></dc:creator>
		<pubDate>Mon, 11 Jun 2012 15:27:30 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4737</guid>
		<description><![CDATA[[...] http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/ [...]]]></description>
		<content:encoded><![CDATA[<p>[...] <a href="http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/" rel="nofollow">http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/</a> [...]</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: damirvadas</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4651</link>
		<dc:creator><![CDATA[damirvadas]]></dc:creator>
		<pubDate>Tue, 24 Apr 2012 14:35:41 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4651</guid>
		<description><![CDATA[Oracle doc:
http://docs.oracle.com/cd/B12037_01/server.101/b10736/indexes.htm
&quot;Unlike most other types of indexes, bitmap indexes include rows that have NULL values. &quot;

And now what?
:-)

Rg,
Damir]]></description>
		<content:encoded><![CDATA[<p>Oracle doc:<br />
<a href="http://docs.oracle.com/cd/B12037_01/server.101/b10736/indexes.htm" rel="nofollow">http://docs.oracle.com/cd/B12037_01/server.101/b10736/indexes.htm</a><br />
&#8220;Unlike most other types of indexes, bitmap indexes include rows that have NULL values. &#8221;</p>
<p>And now what? <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
<p>Rg,<br />
Damir</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4490</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Thu, 01 Mar 2012 00:52:12 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4490</guid>
		<description><![CDATA[David,

Thank you for posting a link to your article - always good to see links to articles that demonstrate helpful information.  You certainly did capture all of the options.

The first time I read the article, my eyes became a bit blurry... why would Oracle&#039;s optimizer select to use an INDEX FULL SCAN operation, which reads one block at a time (from disk), when it can just about as quickly read 8, 16, or 128 8KB blocks at a time (from disk) during a FULL TABLE SCAN operation.  Then it hit me on the second read through... your test table only had 10 very narrow rows - the indexes probably contained only an index root block.

I extended your test case to 1,000,000 rows, with each row being much wider (column C_2 is padded to 255 characters) (tested on 11.2.0.2, I am explaining the steps for the other people who may read this comment, not necessarily for David):
&lt;pre&gt;
create table test1(
       c_1 number not null,
       c_2 varchar2(255)
  );
 
INSERT INTO
  TEST1
SELECT
  ROWNUM,
  RPAD(&#039;Test &#039;&#124;&#124;ROWNUM,255,&#039;A&#039;)
FROM
  DUAL
CONNECT BY
  LEVEL&lt;=1000000;
 
create index test1_c_2 on test1(c_2);
create index test1_c_1 on test1(c_1);
 
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;&#039;TEST1&#039;,CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;NULL)
 
SET AUTOTRACE TRACEONLY STATISTICS EXPLAIN
SET LINESIZE 120
SET PAGESIZE 1000
&lt;/pre&gt;
 
Will the optimizer select to use the TEST1_C_2 index if c_2 IS NOT NULL is specified in the WHERE clause:
&lt;pre&gt;
SELECT
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
&#124; Id  &#124; Operation         &#124; Name  &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
---------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT  &#124;       &#124;  1000K&#124;   244M&#124; 13143   (5)&#124; 00:00:06 &#124;
&#124;*  1 &#124;  TABLE ACCESS FULL&#124; TEST1 &#124;  1000K&#124;   244M&#124; 13143   (5)&#124; 00:00:06 &#124;
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(&quot;C_2&quot; IS NOT NULL)
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     103774  consistent gets
          0  physical reads
          0  redo size
  264867023  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
Well, in this case a FULL TABLE SCAN operation was selected with a calculated cost of 13,143.

Let&#039;s force an index access path:
&lt;pre&gt;
SELECT /*+ INDEX(TEST1) */
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2529630288
 
------------------------------------------------------------------------------
&#124; Id  &#124; Operation        &#124; Name      &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT &#124;           &#124;  1000K&#124;   244M&#124; 54890   (2)&#124; 00:00:22 &#124;
&#124;*  1 &#124;  INDEX FULL SCAN &#124; TEST1_C_2 &#124;  1000K&#124;   244M&#124; 54890   (2)&#124; 00:00:22 &#124;
------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(&quot;C_2&quot; IS NOT NULL)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
     117174  consistent gets
          0  physical reads
          0  redo size
  264867023  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
An INDEX FULL SCAN operation was used, but it was not automatically selected because the 54,890 cost is greater than the 13,143 cost for the FULL TABLE SCAN operation.

Let&#039;s try again with a narrower version of the table, with column C_2 restricted to 10 characters:
&lt;pre&gt;
TRUNCATE TABLE TEST1;
 
INSERT INTO
  TEST1
SELECT
  ROWNUM,
  RPAD(&#039;Test &#039;&#124;&#124;ROWNUM,10,&#039;A&#039;)
FROM
  DUAL
CONNECT BY
  LEVEL&lt;=1000000;
  
COMMIT;
 
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;&#039;TEST1&#039;,CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;NULL,NO_INVALIDATE=&gt;FALSE)
&lt;/pre&gt;
 
Repeating the test with C_2 IS NOT NULL specified in the WHERE clause:
&lt;pre&gt;
SELECT
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
&#124; Id  &#124; Operation         &#124; Name  &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
---------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT  &#124;       &#124;  1000K&#124;    10M&#124;  1239  (23)&#124; 00:00:01 &#124;
&#124;*  1 &#124;  TABLE ACCESS FULL&#124; TEST1 &#124;  1000K&#124;    10M&#124;  1239  (23)&#124; 00:00:01 &#124;
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(&quot;C_2&quot; IS NOT NULL)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      69372  consistent gets
          0  physical reads
          0  redo size
   17867023  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
A full table scan again, this time with a calculated cost of 1,239.

Let&#039;s force an index access path:
&lt;pre&gt;
SELECT /*+ INDEX(TEST1) */
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2529630288
 
------------------------------------------------------------------------------
&#124; Id  &#124; Operation        &#124; Name      &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT &#124;           &#124;  1000K&#124;    10M&#124;  5472   (6)&#124; 00:00:03 &#124;
&#124;*  1 &#124;  INDEX FULL SCAN &#124; TEST1_C_2 &#124;  1000K&#124;    10M&#124;  5472   (6)&#124; 00:00:03 &#124;
------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(&quot;C_2&quot; IS NOT NULL)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      71481  consistent gets
          0  physical reads
          0  redo size
   11833627  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
The INDEX FULL SCAN operation had a calculated cost of 5,472, which is more than the 1,239 for the FULL TABLE SCAN operation, so that is why the full table scan was selected.

Let&#039;s check selecting column C_1 to see if we also experience a FULL TABLE SCAN for this query:
&lt;pre&gt;
SELECT
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2062908727
 
----------------------------------------------------------------------------------
&#124; Id  &#124; Operation            &#124; Name      &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
----------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT     &#124;           &#124;  1000K&#124;  4882K&#124;   921  (20)&#124; 00:00:01 &#124;
&#124;   1 &#124;  INDEX FAST FULL SCAN&#124; TEST1_C_1 &#124;  1000K&#124;  4882K&#124;   921  (20)&#124; 00:00:01 &#124;
----------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68759  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
No full table scan, but we did receive an INDEX FAST FULL SCAN operation, which performs multi-block reads (from disk) of the index blocks, much like what would happen with the table blocks during a FULL TABLE SCAN operation.

Let&#039;s see what happens if we try to force an index access path using a hint for the same query:
&lt;pre&gt;
SELECT /*+ INDEX(TEST1) */
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2345822858
 
------------------------------------------------------------------------------
&#124; Id  &#124; Operation        &#124; Name      &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT &#124;           &#124;  1000K&#124;  4882K&#124;  2509  (12)&#124; 00:00:02 &#124;
&#124;   1 &#124;  INDEX FULL SCAN &#124; TEST1_C_1 &#124;  1000K&#124;  4882K&#124;  2509  (12)&#124; 00:00:02 &#124;
------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68746  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
Notice that the cost increased dramatically, and that the execution plan shows an INDEX FULL SCAN operation rather than an INDEX FAST FULL SCAN operation.

For fun, let&#039;s check the calculated cost of a FULL TABLE SCAN so that we are able to compare the cost with the INDEX FULL SCAN operation:
&lt;pre&gt;
SELECT /*+ FULL(TEST1) */
  C_1
FROM
  TEST1
WHERE
  C_1 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
&#124; Id  &#124; Operation         &#124; Name  &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
---------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT  &#124;       &#124;  1000K&#124;  4882K&#124;  1187  (19)&#124; 00:00:01 &#124;
&#124;   1 &#124;  TABLE ACCESS FULL&#124; TEST1 &#124;  1000K&#124;  4882K&#124;  1187  (19)&#124; 00:00:01 &#124;
---------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      69372  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
The cost of the FULL TABLE SCAN is less than that of the INDEX FULL SCAN operation - so if the INDEX FAST FULL SCAN operation was not legal, the FULL TABLE SCAN operation would have been selected.

Maybe it is a problem with skewed system statistics?
&lt;pre&gt;
COLUMN PNAME FORMAT A20
 
SELECT
  PNAME,
  PVAL1
FROM
  SYS.AUX_STATS$;
 
PNAME                     PVAL1
-------------------- ----------
STATUS
DSTART
DSTOP
FLAGS                         1
CPUSPEEDNW           2116.57559
IOSEEKTIM                    10
IOTFRSPEED                 4096
SREADTIM                     .4
MREADTIM                     .8
CPUSPEED                   1922
MBRC                          6
MAXTHR                155910144
SLAVETHR
&lt;/pre&gt;
The MBRC is just 6, so the optimizer definitely was not under-costing the FULL TABLE SCAN (and INDEX FAST FULL SCAN) operation.

Let&#039;s bump the MBRC to 16 just to see what happens:
&lt;pre&gt;
EXEC DBMS_STATS.SET_SYSTEM_STATS(&#039;MBRC&#039;,16)
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;&#039;TEST1&#039;,CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;NULL,NO_INVALIDATE=&gt;FALSE)
 
SET AUTOTRACE TRACEONLY STATISTICS EXPLAIN
 
SELECT
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2062908727
 
----------------------------------------------------------------------------------
&#124; Id  &#124; Operation            &#124; Name      &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
----------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT     &#124;           &#124;  1000K&#124;  4882K&#124;   457  (39)&#124; 00:00:01 &#124;
&#124;   1 &#124;  INDEX FAST FULL SCAN&#124; TEST1_C_1 &#124;  1000K&#124;  4882K&#124;   457  (39)&#124; 00:00:01 &#124;
----------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68759  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
The INDEX FAST FULL SCAN operation&#039;s cost dropped from 921 to 457.

So, what would happen if an INDEX FAST FULL SCAN operation were not valid for this SQL statement?:
&lt;pre&gt;
SELECT /*+ NO_INDEX_FFS(TEST1) */
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
&#124; Id  &#124; Operation         &#124; Name  &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
---------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT  &#124;       &#124;  1000K&#124;  4882K&#124;   585  (38)&#124; 00:00:01 &#124;
&#124;   1 &#124;  TABLE ACCESS FULL&#124; TEST1 &#124;  1000K&#124;  4882K&#124;   585  (38)&#124; 00:00:01 &#124;
---------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      69372  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
A full table scan with a cost of 585.

Let&#039;s switch back to an INDEX FULL SCAN operation:
&lt;pre&gt;
SELECT /*+ INDEX(TEST1) */
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2345822858
 
------------------------------------------------------------------------------
&#124; Id  &#124; Operation        &#124; Name      &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT &#124;           &#124;  1000K&#124;  4882K&#124;  2509  (12)&#124; 00:00:02 &#124;
&#124;   1 &#124;  INDEX FULL SCAN &#124; TEST1_C_1 &#124;  1000K&#124;  4882K&#124;  2509  (12)&#124; 00:00:02 &#124;
------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68746  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
&lt;/pre&gt;
The calculated cost is unchanged after the change of the MBRC system statistic.

---

David,

Would you be able to re-run your test with Oracle Database 11.2.0.1, 11.2.0.2, or 11.2.0.3 with a larger number of rows?  I am curious to know if you will see INDEX FAST FULL SCAN operations, INDEX FULL SCAN operations, or TABLE ACCESS FULL operations in the execution plans.]]></description>
		<content:encoded><![CDATA[<p>David,</p>
<p>Thank you for posting a link to your article &#8211; always good to see links to articles that demonstrate helpful information.  You certainly did capture all of the options.</p>
<p>The first time I read the article, my eyes became a bit blurry&#8230; why would Oracle&#8217;s optimizer select to use an INDEX FULL SCAN operation, which reads one block at a time (from disk), when it can just about as quickly read 8, 16, or 128 8KB blocks at a time (from disk) during a FULL TABLE SCAN operation.  Then it hit me on the second read through&#8230; your test table only had 10 very narrow rows &#8211; the indexes probably contained only an index root block.</p>
<p>I extended your test case to 1,000,000 rows, with each row being much wider (column C_2 is padded to 255 characters) (tested on 11.2.0.2, I am explaining the steps for the other people who may read this comment, not necessarily for David):</p>
<pre>
create table test1(
       c_1 number not null,
       c_2 varchar2(255)
  );
 
INSERT INTO
  TEST1
SELECT
  ROWNUM,
  RPAD('Test '||ROWNUM,255,'A')
FROM
  DUAL
CONNECT BY
  LEVEL&lt;=1000000;
 
create index test1_c_2 on test1(c_2);
create index test1_c_1 on test1(c_1);
 
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;'TEST1',CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;NULL)
 
SET AUTOTRACE TRACEONLY STATISTICS EXPLAIN
SET LINESIZE 120
SET PAGESIZE 1000
</pre>
<p>Will the optimizer select to use the TEST1_C_2 index if c_2 IS NOT NULL is specified in the WHERE clause:</p>
<pre>
SELECT
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |  1000K|   244M| 13143   (5)| 00:00:06 |
|*  1 |  TABLE ACCESS FULL| TEST1 |  1000K|   244M| 13143   (5)| 00:00:06 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("C_2" IS NOT NULL)
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     103774  consistent gets
          0  physical reads
          0  redo size
  264867023  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>Well, in this case a FULL TABLE SCAN operation was selected with a calculated cost of 13,143.</p>
<p>Let&#8217;s force an index access path:</p>
<pre>
SELECT /*+ INDEX(TEST1) */
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2529630288
 
------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |  1000K|   244M| 54890   (2)| 00:00:22 |
|*  1 |  INDEX FULL SCAN | TEST1_C_2 |  1000K|   244M| 54890   (2)| 00:00:22 |
------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("C_2" IS NOT NULL)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
     117174  consistent gets
          0  physical reads
          0  redo size
  264867023  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>An INDEX FULL SCAN operation was used, but it was not automatically selected because the 54,890 cost is greater than the 13,143 cost for the FULL TABLE SCAN operation.</p>
<p>Let&#8217;s try again with a narrower version of the table, with column C_2 restricted to 10 characters:</p>
<pre>
TRUNCATE TABLE TEST1;
 
INSERT INTO
  TEST1
SELECT
  ROWNUM,
  RPAD('Test '||ROWNUM,10,'A')
FROM
  DUAL
CONNECT BY
  LEVEL&lt;=1000000;
  
COMMIT;
 
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;'TEST1',CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;NULL,NO_INVALIDATE=&gt;FALSE)
</pre>
<p>Repeating the test with C_2 IS NOT NULL specified in the WHERE clause:</p>
<pre>
SELECT
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |  1000K|    10M|  1239  (23)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST1 |  1000K|    10M|  1239  (23)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("C_2" IS NOT NULL)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      69372  consistent gets
          0  physical reads
          0  redo size
   17867023  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>A full table scan again, this time with a calculated cost of 1,239.</p>
<p>Let&#8217;s force an index access path:</p>
<pre>
SELECT /*+ INDEX(TEST1) */
  C_2
FROM
  TEST1
WHERE
  C_2 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2529630288
 
------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |  1000K|    10M|  5472   (6)| 00:00:03 |
|*  1 |  INDEX FULL SCAN | TEST1_C_2 |  1000K|    10M|  5472   (6)| 00:00:03 |
------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("C_2" IS NOT NULL)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      71481  consistent gets
          0  physical reads
          0  redo size
   11833627  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>The INDEX FULL SCAN operation had a calculated cost of 5,472, which is more than the 1,239 for the FULL TABLE SCAN operation, so that is why the full table scan was selected.</p>
<p>Let&#8217;s check selecting column C_1 to see if we also experience a FULL TABLE SCAN for this query:</p>
<pre>
SELECT
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2062908727
 
----------------------------------------------------------------------------------
| Id  | Operation            | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |           |  1000K|  4882K|   921  (20)| 00:00:01 |
|   1 |  INDEX FAST FULL SCAN| TEST1_C_1 |  1000K|  4882K|   921  (20)| 00:00:01 |
----------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68759  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>No full table scan, but we did receive an INDEX FAST FULL SCAN operation, which performs multi-block reads (from disk) of the index blocks, much like what would happen with the table blocks during a FULL TABLE SCAN operation.</p>
<p>Let&#8217;s see what happens if we try to force an index access path using a hint for the same query:</p>
<pre>
SELECT /*+ INDEX(TEST1) */
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2345822858
 
------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |  1000K|  4882K|  2509  (12)| 00:00:02 |
|   1 |  INDEX FULL SCAN | TEST1_C_1 |  1000K|  4882K|  2509  (12)| 00:00:02 |
------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68746  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>Notice that the cost increased dramatically, and that the execution plan shows an INDEX FULL SCAN operation rather than an INDEX FAST FULL SCAN operation.</p>
<p>For fun, let&#8217;s check the calculated cost of a FULL TABLE SCAN so that we are able to compare the cost with the INDEX FULL SCAN operation:</p>
<pre>
SELECT /*+ FULL(TEST1) */
  C_1
FROM
  TEST1
WHERE
  C_1 IS NOT NULL;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |  1000K|  4882K|  1187  (19)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| TEST1 |  1000K|  4882K|  1187  (19)| 00:00:01 |
---------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      69372  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>The cost of the FULL TABLE SCAN is less than that of the INDEX FULL SCAN operation &#8211; so if the INDEX FAST FULL SCAN operation was not legal, the FULL TABLE SCAN operation would have been selected.</p>
<p>Maybe it is a problem with skewed system statistics?</p>
<pre>
COLUMN PNAME FORMAT A20
 
SELECT
  PNAME,
  PVAL1
FROM
  SYS.AUX_STATS$;
 
PNAME                     PVAL1
-------------------- ----------
STATUS
DSTART
DSTOP
FLAGS                         1
CPUSPEEDNW           2116.57559
IOSEEKTIM                    10
IOTFRSPEED                 4096
SREADTIM                     .4
MREADTIM                     .8
CPUSPEED                   1922
MBRC                          6
MAXTHR                155910144
SLAVETHR
</pre>
<p>The MBRC is just 6, so the optimizer definitely was not under-costing the FULL TABLE SCAN (and INDEX FAST FULL SCAN) operation.</p>
<p>Let&#8217;s bump the MBRC to 16 just to see what happens:</p>
<pre>
EXEC DBMS_STATS.SET_SYSTEM_STATS('MBRC',16)
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;'TEST1',CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;NULL,NO_INVALIDATE=&gt;FALSE)
 
SET AUTOTRACE TRACEONLY STATISTICS EXPLAIN
 
SELECT
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2062908727
 
----------------------------------------------------------------------------------
| Id  | Operation            | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |           |  1000K|  4882K|   457  (39)| 00:00:01 |
|   1 |  INDEX FAST FULL SCAN| TEST1_C_1 |  1000K|  4882K|   457  (39)| 00:00:01 |
----------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68759  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>The INDEX FAST FULL SCAN operation&#8217;s cost dropped from 921 to 457.</p>
<p>So, what would happen if an INDEX FAST FULL SCAN operation were not valid for this SQL statement?:</p>
<pre>
SELECT /*+ NO_INDEX_FFS(TEST1) */
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4122059633
 
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |  1000K|  4882K|   585  (38)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| TEST1 |  1000K|  4882K|   585  (38)| 00:00:01 |
---------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      69372  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>A full table scan with a cost of 585.</p>
<p>Let&#8217;s switch back to an INDEX FULL SCAN operation:</p>
<pre>
SELECT /*+ INDEX(TEST1) */
  C_1
FROM
  TEST1;
 
1000000 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2345822858
 
------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |  1000K|  4882K|  2509  (12)| 00:00:02 |
|   1 |  INDEX FULL SCAN | TEST1_C_1 |  1000K|  4882K|  2509  (12)| 00:00:02 |
------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      68746  consistent gets
          0  physical reads
          0  redo size
   11846825  bytes sent via SQL*Net to client
     733686  bytes received via SQL*Net from client
      66668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
    1000000  rows processed
</pre>
<p>The calculated cost is unchanged after the change of the MBRC system statistic.</p>
<p>&#8212;</p>
<p>David,</p>
<p>Would you be able to re-run your test with Oracle Database 11.2.0.1, 11.2.0.2, or 11.2.0.3 with a larger number of rows?  I am curious to know if you will see INDEX FAST FULL SCAN operations, INDEX FULL SCAN operations, or TABLE ACCESS FULL operations in the execution plans.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: David Fitzjarrell (@ddfdba)</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4488</link>
		<dc:creator><![CDATA[David Fitzjarrell (@ddfdba)]]></dc:creator>
		<pubDate>Wed, 29 Feb 2012 20:36:07 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4488</guid>
		<description><![CDATA[Proved this almost four years ago on my own blog:  

http://oratips-ddf.blogspot.com/2008/04/tale-of-two-indexes.html]]></description>
		<content:encoded><![CDATA[<p>Proved this almost four years ago on my own blog:  </p>
<p><a href="http://oratips-ddf.blogspot.com/2008/04/tale-of-two-indexes.html" rel="nofollow">http://oratips-ddf.blogspot.com/2008/04/tale-of-two-indexes.html</a></p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Narendra</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4486</link>
		<dc:creator><![CDATA[Narendra]]></dc:creator>
		<pubDate>Wed, 29 Feb 2012 16:50:59 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4486</guid>
		<description><![CDATA[Charles,
&lt;i&gt;Back to the original test, and a fix.&lt;/i&gt;
Nice one. What do you get if you convert NULL and not C3 ? :)]]></description>
		<content:encoded><![CDATA[<p>Charles,<br />
<i>Back to the original test, and a fix.</i><br />
Nice one. What do you get if you convert NULL and not C3 ? <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4485</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Wed, 29 Feb 2012 16:36:19 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4485</guid>
		<description><![CDATA[Narendra,

Well done.  Another case where a single test case is insufficient, especially if an intentional (or unintentional) mistake is made in the test case  :-)

&lt;pre&gt;
SQL&gt; SELECT COLUMN_EXPRESSION FROM USER_IND_EXPRESSIONS WHERE INDEX_NAME=&#039;IND_T2_C3_FN&#039;;
 
COLUMN_EXPRESSION
----------------------------------------
DECODE(TO_CHAR(&quot;C3&quot;,&#039;DD-MON-RR&#039;),NULL,1)
&lt;/pre&gt;
Well, that is not what I requested - the problem extends beyond problems with CURSOR_SHARING=FORCE - NLS settings could also be an issue.

Back to the original test, and a fix.
&lt;pre&gt;
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2=&#039;D00000000000000&#039;
  AND DECODE(C3,NULL,1)=1;
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,&#039;TYPICAL&#039;));
 
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2=&#039;D00000000000000&#039;
  AND DECODE(TO_CHAR(&quot;C3&quot;,&#039;DD-MON-RR&#039;),NULL,1)=1;
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,&#039;TYPICAL&#039;));
&lt;/pre&gt; 

Results:
&lt;pre&gt;
SQL_ID  8psj7gcwcn72m, child number 0
-------------------------------------
SELECT /*+ INDEX(T2) */   C1,   C2,   C3,   NVL2(C3,NULL,1) C4 FROM
T2 WHERE   C2=&#039;D00000000000000&#039;   AND DECODE(C3,NULL,1)=1
 
Plan hash value: 1513984157
 
--------------------------------------------------------------------------
&#124; Id  &#124; Operation         &#124; Name &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
--------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT  &#124;      &#124;       &#124;       &#124;  3346 (100)&#124;          &#124;
&#124;*  1 &#124;  TABLE ACCESS FULL&#124; T2   &#124;   769 &#124; 23070 &#124;  3346   (1)&#124; 00:00:01 &#124;
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter((&quot;C2&quot;=&#039;D00000000000000&#039; AND
              DECODE(INTERNAL_FUNCTION(&quot;C3&quot;),NULL,1)=1))
 
---
 
SQL_ID  8av2y4gms3baq, child number 0
-------------------------------------
SELECT /*+ INDEX(T2) */   C1,   C2,   C3,   NVL2(C3,NULL,1) C4 FROM
T2 WHERE   C2=&#039;D00000000000000&#039;   AND
DECODE(TO_CHAR(&quot;C3&quot;,&#039;DD-MON-RR&#039;),NULL,1)=1
 
Plan hash value: 941108248
 
--------------------------------------------------------------------------------------------
&#124; Id  &#124; Operation                   &#124; Name         &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time     &#124;
--------------------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT            &#124;              &#124;       &#124;       &#124; 10024 (100)&#124;          &#124;
&#124;*  1 &#124;  TABLE ACCESS BY INDEX ROWID&#124; T2           &#124;   100 &#124;  3200 &#124; 10024   (1)&#124; 00:00:01 &#124;
&#124;*  2 &#124;   INDEX RANGE SCAN          &#124; IND_T2_C3_FN &#124; 10000 &#124;       &#124;    20   (0)&#124; 00:00:01 &#124;
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(&quot;C2&quot;=&#039;D00000000000000&#039;)
   2 - access(&quot;T2&quot;.&quot;SYS_NC00005$&quot;=1)
&lt;/pre&gt;]]></description>
		<content:encoded><![CDATA[<p>Narendra,</p>
<p>Well done.  Another case where a single test case is insufficient, especially if an intentional (or unintentional) mistake is made in the test case  <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
<pre>
SQL&gt; SELECT COLUMN_EXPRESSION FROM USER_IND_EXPRESSIONS WHERE INDEX_NAME='IND_T2_C3_FN';
 
COLUMN_EXPRESSION
----------------------------------------
DECODE(TO_CHAR("C3",'DD-MON-RR'),NULL,1)
</pre>
<p>Well, that is not what I requested &#8211; the problem extends beyond problems with CURSOR_SHARING=FORCE &#8211; NLS settings could also be an issue.</p>
<p>Back to the original test, and a fix.</p>
<pre>
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2='D00000000000000'
  AND DECODE(C3,NULL,1)=1;
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'TYPICAL'));
 
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2='D00000000000000'
  AND DECODE(TO_CHAR("C3",'DD-MON-RR'),NULL,1)=1;
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'TYPICAL'));
</pre>
<p>Results:</p>
<pre>
SQL_ID  8psj7gcwcn72m, child number 0
-------------------------------------
SELECT /*+ INDEX(T2) */   C1,   C2,   C3,   NVL2(C3,NULL,1) C4 FROM
T2 WHERE   C2='D00000000000000'   AND DECODE(C3,NULL,1)=1
 
Plan hash value: 1513984157
 
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |  3346 (100)|          |
|*  1 |  TABLE ACCESS FULL| T2   |   769 | 23070 |  3346   (1)| 00:00:01 |
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(("C2"='D00000000000000' AND
              DECODE(INTERNAL_FUNCTION("C3"),NULL,1)=1))
 
---
 
SQL_ID  8av2y4gms3baq, child number 0
-------------------------------------
SELECT /*+ INDEX(T2) */   C1,   C2,   C3,   NVL2(C3,NULL,1) C4 FROM
T2 WHERE   C2='D00000000000000'   AND
DECODE(TO_CHAR("C3",'DD-MON-RR'),NULL,1)=1
 
Plan hash value: 941108248
 
--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       | 10024 (100)|          |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T2           |   100 |  3200 | 10024   (1)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IND_T2_C3_FN | 10000 |       |    20   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("C2"='D00000000000000')
   2 - access("T2"."SYS_NC00005$"=1)
</pre>
]]></content:encoded>
	</item>
	<item>
		<title>By: Narendra</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4484</link>
		<dc:creator><![CDATA[Narendra]]></dc:creator>
		<pubDate>Wed, 29 Feb 2012 16:36:09 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4484</guid>
		<description><![CDATA[Charles,

Apologies but the INTERNAL_FUNCTION appears because C3 is DATE datatype and it is being compared to NULL (which, by default, is VARCHAR2, I think). Hence the conversion for data type compatibility.]]></description>
		<content:encoded><![CDATA[<p>Charles,</p>
<p>Apologies but the INTERNAL_FUNCTION appears because C3 is DATE datatype and it is being compared to NULL (which, by default, is VARCHAR2, I think). Hence the conversion for data type compatibility.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Narendra</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4483</link>
		<dc:creator><![CDATA[Narendra]]></dc:creator>
		<pubDate>Wed, 29 Feb 2012 16:28:31 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4483</guid>
		<description><![CDATA[Charles,

Guess what? I don&#039;t get table scan when using DECODE. My results are same as that using NVL (again, apologies for not formatting)
&lt;pre&gt;
SQL&gt; drop INDEX IND_T2_C3_FN ;

Index dropped.

SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 DECODE(C3,NULL,1);

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;&#039;T2&#039;,CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;&#039;FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254&#039;,NO_INVALIDATE=&gt;FALSE) ;

PL/SQL procedure successfully completed.

SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME=&#039;T2&#039;;

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00006$		       YES YES
SYS_NC00005$		       YES YES

6 rows selected.

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = &#039;T2&#039; ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       NULL
IND_T2_C3_FN		       1

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2=&#039;D00000000000000&#039;
  AND DECODE(C3,NULL,1)=1;
 10  
Execution Plan
----------------------------------------------------------
Plan hash value: 2303772603

--------------------------------------------------------------------------------------------
&#124; Id  &#124; Operation		    &#124; Name	   &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time	   &#124;
--------------------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT	    &#124;		   &#124;   100 &#124;  3000 &#124;  3743   (1)&#124; 00:00:45 &#124;
&#124;*  1 &#124;  TABLE ACCESS BY INDEX ROWID&#124; T2	   &#124;   100 &#124;  3000 &#124;  3743   (1)&#124; 00:00:45 &#124;
&#124;*  2 &#124;   INDEX FULL SCAN	    &#124; IND_T2_C3_FN &#124; 10000 &#124;	   &#124;  3225   (1)&#124; 00:00:39 &#124;
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(&quot;C2&quot;=&#039;D00000000000000&#039;)
   2 - filter(DECODE(INTERNAL_FUNCTION(&quot;C3&quot;),NULL,1)=1)

SQL&gt; DROP INDEX IND_T2_C3_FN;

Index dropped.

SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 (DECODE(C3,NULL,1)) nologging ;

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;&#039;T2&#039;,CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;&#039;FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254&#039;,NO_INVALIDATE=&gt;FALSE) ;

PL/SQL procedure successfully completed.

SQL&gt; set autotrace off
SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME=&#039;T2&#039;;

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00005$		       YES YES

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = &#039;T2&#039; ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       DECODE(&quot;C3&quot;,NULL,1)

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2=&#039;D00000000000000&#039;
 10    AND DECODE(C3,NULL,1)=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 941108248

--------------------------------------------------------------------------------------------
&#124; Id  &#124; Operation		    &#124; Name	   &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time	   &#124;
--------------------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT	    &#124;		   &#124;   100 &#124;  3000 &#124; 10023   (1)&#124; 00:02:01 &#124;
&#124;*  1 &#124;  TABLE ACCESS BY INDEX ROWID&#124; T2	   &#124;   100 &#124;  3000 &#124; 10023   (1)&#124; 00:02:01 &#124;
&#124;*  2 &#124;   INDEX RANGE SCAN	    &#124; IND_T2_C3_FN &#124; 10000 &#124;	   &#124;	20   (0)&#124; 00:00:01 &#124;
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(&quot;C2&quot;=&#039;D00000000000000&#039;)
   2 - access(DECODE(INTERNAL_FUNCTION(&quot;C3&quot;),NULL,1)=1)
&lt;/pre&gt;]]></description>
		<content:encoded><![CDATA[<p>Charles,</p>
<p>Guess what? I don&#8217;t get table scan when using DECODE. My results are same as that using NVL (again, apologies for not formatting)</p>
<pre>
SQL&gt; drop INDEX IND_T2_C3_FN ;

Index dropped.

SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 DECODE(C3,NULL,1);

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;'T2',CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;'FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254',NO_INVALIDATE=&gt;FALSE) ;

PL/SQL procedure successfully completed.

SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME='T2';

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00006$		       YES YES
SYS_NC00005$		       YES YES

6 rows selected.

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = 'T2' ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       NULL
IND_T2_C3_FN		       1

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2='D00000000000000'
  AND DECODE(C3,NULL,1)=1;
 10  
Execution Plan
----------------------------------------------------------
Plan hash value: 2303772603

--------------------------------------------------------------------------------------------
| Id  | Operation		    | Name	   | Rows  | Bytes | Cost (%CPU)| Time	   |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT	    |		   |   100 |  3000 |  3743   (1)| 00:00:45 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T2	   |   100 |  3000 |  3743   (1)| 00:00:45 |
|*  2 |   INDEX FULL SCAN	    | IND_T2_C3_FN | 10000 |	   |  3225   (1)| 00:00:39 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='D00000000000000')
   2 - filter(DECODE(INTERNAL_FUNCTION("C3"),NULL,1)=1)

SQL&gt; DROP INDEX IND_T2_C3_FN;

Index dropped.

SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 (DECODE(C3,NULL,1)) nologging ;

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;'T2',CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;'FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254',NO_INVALIDATE=&gt;FALSE) ;

PL/SQL procedure successfully completed.

SQL&gt; set autotrace off
SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME='T2';

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00005$		       YES YES

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = 'T2' ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       DECODE("C3",NULL,1)

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2='D00000000000000'
 10    AND DECODE(C3,NULL,1)=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 941108248

--------------------------------------------------------------------------------------------
| Id  | Operation		    | Name	   | Rows  | Bytes | Cost (%CPU)| Time	   |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT	    |		   |   100 |  3000 | 10023   (1)| 00:02:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T2	   |   100 |  3000 | 10023   (1)| 00:02:01 |
|*  2 |   INDEX RANGE SCAN	    | IND_T2_C3_FN | 10000 |	   |	20   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='D00000000000000')
   2 - access(DECODE(INTERNAL_FUNCTION("C3"),NULL,1)=1)
</pre>
]]></content:encoded>
	</item>
	<item>
		<title>By: Narendra</title>
		<link>http://hoopercharles.wordpress.com/2012/02/28/repeat-after-me-null-values-are-not-stored-in-indexes/#comment-4482</link>
		<dc:creator><![CDATA[Narendra]]></dc:creator>
		<pubDate>Wed, 29 Feb 2012 16:15:39 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=6108#comment-4482</guid>
		<description><![CDATA[Here is my test on a 10.2.0.5 db (Can&#039;t remember how to format as code). But you can see the difference when, as you said, brackets are used or not.
&lt;pre&gt;
SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 NVL2(C3,NULL,1);

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;&#039;T2&#039;,CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;&#039;FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254&#039;,NO_INVALIDATE=&gt;FALSE)
;

PL/SQL procedure successfully completed.

SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME=&#039;T2&#039;;

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00005$		       YES YES
SYS_NC00006$		       YES YES

6 rows selected.

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = &#039;T2&#039; ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       NULL
IND_T2_C3_FN		       1

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2=&#039;D00000000000000&#039;
 10    AND NVL2(C3,NULL,1)=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 2303772603

--------------------------------------------------------------------------------------------
&#124; Id  &#124; Operation		    &#124; Name	   &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time	   &#124;
--------------------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT	    &#124;		   &#124;   100 &#124;  3000 &#124;  3734   (1)&#124; 00:00:45 &#124;
&#124;*  1 &#124;  TABLE ACCESS BY INDEX ROWID&#124; T2	   &#124;   100 &#124;  3000 &#124;  3734   (1)&#124; 00:00:45 &#124;
&#124;*  2 &#124;   INDEX FULL SCAN	    &#124; IND_T2_C3_FN &#124; 10000 &#124;	   &#124;  3216   (1)&#124; 00:00:39 &#124;
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(&quot;C2&quot;=&#039;D00000000000000&#039;)
   2 - filter(NVL2(&quot;C3&quot;,NULL,1)=1)

SQL&gt; set autotrace off
SQL&gt; DROP INDEX IND_T2_C3_FN;

Index dropped.

SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 (NVL2(C3,NULL,1)) nologging ;

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;&#039;T2&#039;,CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;&#039;FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254&#039;,NO_INVALIDATE=&gt;FALSE) ;

PL/SQL procedure successfully completed.

SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME=&#039;T2&#039;;

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00005$		       YES YES

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = &#039;T2&#039; ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       NVL2(&quot;C3&quot;,NULL,1)

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2=&#039;D00000000000000&#039;
  AND NVL2(C3,NULL,1)=1;
 10  
Execution Plan
----------------------------------------------------------
Plan hash value: 941108248

--------------------------------------------------------------------------------------------
&#124; Id  &#124; Operation		    &#124; Name	   &#124; Rows  &#124; Bytes &#124; Cost (%CPU)&#124; Time	   &#124;
--------------------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT	    &#124;		   &#124;   100 &#124;  3000 &#124; 10023   (1)&#124; 00:02:01 &#124;
&#124;*  1 &#124;  TABLE ACCESS BY INDEX ROWID&#124; T2	   &#124;   100 &#124;  3000 &#124; 10023   (1)&#124; 00:02:01 &#124;
&#124;*  2 &#124;   INDEX RANGE SCAN	    &#124; IND_T2_C3_FN &#124; 10000 &#124;	   &#124;	20   (0)&#124; 00:00:01 &#124;
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(&quot;C2&quot;=&#039;D00000000000000&#039;)
   2 - access(NVL2(&quot;C3&quot;,NULL,1)=1)
&lt;/pre&gt;]]></description>
		<content:encoded><![CDATA[<p>Here is my test on a 10.2.0.5 db (Can&#8217;t remember how to format as code). But you can see the difference when, as you said, brackets are used or not.</p>
<pre>
SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 NVL2(C3,NULL,1);

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;'T2',CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;'FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254',NO_INVALIDATE=&gt;FALSE)
;

PL/SQL procedure successfully completed.

SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME='T2';

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00005$		       YES YES
SYS_NC00006$		       YES YES

6 rows selected.

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = 'T2' ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       NULL
IND_T2_C3_FN		       1

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2='D00000000000000'
 10    AND NVL2(C3,NULL,1)=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 2303772603

--------------------------------------------------------------------------------------------
| Id  | Operation		    | Name	   | Rows  | Bytes | Cost (%CPU)| Time	   |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT	    |		   |   100 |  3000 |  3734   (1)| 00:00:45 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T2	   |   100 |  3000 |  3734   (1)| 00:00:45 |
|*  2 |   INDEX FULL SCAN	    | IND_T2_C3_FN | 10000 |	   |  3216   (1)| 00:00:39 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='D00000000000000')
   2 - filter(NVL2("C3",NULL,1)=1)

SQL&gt; set autotrace off
SQL&gt; DROP INDEX IND_T2_C3_FN;

Index dropped.

SQL&gt; CREATE INDEX IND_T2_C3_FN ON T2 (NVL2(C3,NULL,1)) nologging ;

Index created.

SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=&gt;USER,TABNAME=&gt;'T2',CASCADE=&gt;TRUE,ESTIMATE_PERCENT=&gt;100,METHOD_OPT=&gt;'FOR ALL HIDDEN COLUMNS SIZE 254 FOR ALL INDEXED COLUMNS SIZE 254',NO_INVALIDATE=&gt;FALSE) ;

PL/SQL procedure successfully completed.

SQL&gt; SELECT COLUMN_NAME,HIDDEN_COLUMN,VIRTUAL_COLUMN FROM USER_TAB_COLS WHERE TABLE_NAME='T2';

COLUMN_NAME		       HID VIR
------------------------------ --- ---
C1			       NO  NO
C2			       NO  NO
C3			       NO  NO
C4			       NO  NO
SYS_NC00005$		       YES YES

SQL&gt; select index_name, column_expression from user_ind_expressions where table_name = 'T2' ;

INDEX_NAME		       COLUMN_EXPRESSION
------------------------------ --------------------------------------------------------------------------------
IND_T2_C3_FN		       NVL2("C3",NULL,1)

SQL&gt; set autotrace traceonly explain
SELECT /*+ INDEX(T2) */
  C1,
  C2,
  C3,
  NVL2(C3,NULL,1) C4
FROM
  T2
WHERE
  C2='D00000000000000'
  AND NVL2(C3,NULL,1)=1;
 10  
Execution Plan
----------------------------------------------------------
Plan hash value: 941108248

--------------------------------------------------------------------------------------------
| Id  | Operation		    | Name	   | Rows  | Bytes | Cost (%CPU)| Time	   |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT	    |		   |   100 |  3000 | 10023   (1)| 00:02:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T2	   |   100 |  3000 | 10023   (1)| 00:02:01 |
|*  2 |   INDEX RANGE SCAN	    | IND_T2_C3_FN | 10000 |	   |	20   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2"='D00000000000000')
   2 - access(NVL2("C3",NULL,1)=1)
</pre>
]]></content:encoded>
	</item>
</channel>
</rss>
