Until some time I trully believed that Oracle doesn’t store NULL in Oracle b-tree index. Apparently, this is not exactly true, i.e. false. Oracle doesn’t store null values in index ONLY and ONLY when ALL columns in the index are null. If any of the index columns has not null value, the key is put in the index. My misconcept was so strong that I actually couldn’t belive it until I dumped leaf block and saw myself that Oracle does store index keys with NULL columns.
Yesterday (or day before?) an interesting idea came to my mind while sitting on Tom Kyte’s seminar here in Munich. Statement “IS NULL operator in filter predicates cannot use index becuase there is no guarantee that all not null values are actually within the index” is not correct either. It’s quite easy to make optimizer using index range scan to seek rows with NULL columns. This might be expecially useful if it’s not possible to change application source code. We just need create index including nullable column used in IS NULL and another column declared as NOT NULL. This can be any small not null column or dummy column created for that purpose. This way Oracle will always include NULL values of another column in the index.
And here is an example.
SQL> CREATE TABLE t ( 2 keycol NUMBER(10,0) NOT NULL, 3 c CHAR(1) DEFAULT '1' NOT NULL , 4 ncol NUMBER(6,0) NULL, 5 datacol NUMBER, 6 CONSTRAINT t_pk PRIMARY KEY (keycol) 7 ); Table created. SQL> CREATE INDEX t_ind ON t (ncol,c); Index created. SQL> INSERT INTO t 2 (keycol, ncol, datacol) 3 SELECT ROWNUM, CASE 4 WHEN DBMS_RANDOM.VALUE (0, 1000) COMMIT ; Commit complete. SQL> BEGIN 2 DBMS_STATS.gather_table_stats (USER, 'T'); 3 END; 4 / PL/SQL procedure successfully completed. SQL> SET AUTOTRACE ON SQL> SELECT * 2 FROM t 3 WHERE ncol IS NULL; KEYCOL C NCOL DATACOL ---------- - ---------- ---------- 1111 1 8.9202E+25 4742 1 8.7495E+25 5021 1 8.3964E+25 8956 1 2.2568E+24 269 1 6.8797E+25 487 1 4.5290E+25 7669 1 4.5904E+25 7878 1 1.5710E+25 8 rows selected. Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=8 Bytes=200) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=8 Bytes= 200) 2 1 INDEX (RANGE SCAN) OF 'T_IND' (NON-UNIQUE) (Cost=2 Card=Statistics ---------------------------------------------------------- 5 recursive calls 0 db block gets 16 consistent gets 0 physical reads 72 redo size 903 bytes sent via SQL*Net to client 651 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 2 sorts (memory) 0 sorts (disk) 8 rows processed SQL> SET AUTOTRACE OFF
Now let’s have a look at index leaf block. We need to find out the ROWID if the leaf block with null values on ncol. We don’t need to dump the whole tree thanks to non-documented function sys_op_lbid. Thanks to Jonathan Lewis for mentioning that in Oracl-L thread.
SQL> SELECT object_id 2 FROM user_objects 3 WHERE object_name = 'T_IND'; OBJECT_ID ---------- 1823665 SQL> SELECT DBMS_ROWID.rowid_relative_fno (myrowid) file_no, 2 DBMS_ROWID.rowid_block_number (myrowid) block_no 3 FROM (SELECT sys_op_lbid(1823665, ‘L’, t.ROWID) myrowid 4 FROM t 5 WHERE ncol IS NULL AND ROWNUM FILE_NO BLOCK_NO ———- ———- 3 58521 SQL> ALTER SESSION SET tracefile_identifier = ‘null_in_index’; Session altered. SQL> ALTER SYSTEM DUMP DATAFILE 3 BLOCK 58521; System altered.
Having a look at trace file (it will have ‘null_in_index’ appended to the name) we can actually see that all rows with NULL values in ncol column are referenced in the index:
... skip ... Leaf block dump =============== ... skip ... row#264[7872] flag: —–, lock: 0 col 0; NULL col 1; len 1; (1): 31 col 2; len 6; (6): 00 c1 06 8e 00 0e row#265[7884] flag: —–, lock: 0 col 0; NULL col 1; len 1; (1): 31 col 2; len 6; (6): 00 c1 06 8f 00 d7 … skip … —– end of leaf block dump —–


Hi,
I tried this code but its not working, Please help, thanks in advance
CREATE TABLE t (pcol NUMBER , ncol VARCHAR2(10) NULL , c CHAR(1) DEFAULT ‘1′ NOT NULL );
INSERT INTO t ( pcol , ncol , c ) VALUES ( 1 , ‘name1′ , 1 );
INSERT INTO t ( pcol , ncol , c ) VALUES ( NULL , ‘name2′ , 2 );
INSERT INTO t ( pcol , ncol , c ) VALUES ( 3 , NULL , 3 );
INSERT INTO t ( pcol , ncol , c ) VALUES ( NULL , NULL , 4 );
INSERT INTO t ( pcol , ncol , c ) VALUES ( 4 , NULL , 4 );
INSERT INTO t ( pcol , ncol , c ) VALUES ( 5 , ‘name3′ , 2 );
CREATE INDEX t_ind ON t (ncol,c);
BEGIN
DBMS_STATS.gather_table_stats ( USER , ‘T’);
END;
–This query is not usng index
Select * from t where ncol = 2
–This query is not usng index
Select * from t where ncol is null
I guess this is correct from CBO point of view.
Probably, there are too few rows to use the index so optimizer estimates the cost to be lower to go for FTS. Try running these queries with autotrace and that run them with hint /*+ index(t) */. Than compare the cost of using index vs. FTS.