Há um problema de longa data(?) com o Oracle que, se você quiser usar um DISTINCT, selecione em uma coluna que possui um índice B*-Tree, mas é anulável, que o índice não é usado. (Como outras respostas sugerem, isso é verdade mesmo se houver uma restrição de verificação adicionada após o fato (sem nulos presentes)).
Existem várias maneiras de contornar isso (incluindo usar um índice BITMAP ou adicionar uma segunda coluna NON NULL ou constante ao índice). Porém acabei de notar que se eu fizer o SELECT DISTINCT com WHERE NOT NULL, o Oracle consegue usar o índice (index fast full scan).
Minha pergunta: desde qual versão do Oracle esse comportamento está presente, é confiável (quando o índice de duas colunas é preferido) e por que não é mencionado com mais frequência (por exemplo , esta boa resposta não o menciona)?
Pequeno reprodutor
drop table SCOTT.T;
-- will not work with short rows (SELECT OWNER,SUBOBJECT_NAME ...)
create table SCOTT.T AS SELECT * FROM ALL_OBJECTS;
create index SCOTT.IDX_T_OWNER on SCOTT.T(OWNER); -- NOT NULL
-- (subobject_name,1) or (subobject_name,namespace) is NULL, NOT NULL
create index SCOTT.IDX_T_SUBOBJ on SCOTT.T(subobject_name); -- NULL
exec dbms_stats.gather_table_stats(OWNNAME=>'SCOTT',TABNAME=>'T', cascade=>true );
desc SCOTT.T;
set autotrace on explain
-- fast index scan:
select distinct OWNER from SCOTT.T;
-- full table scan:
select distinct SUBOBJECT_NAME from SCOTT.T;
-- fast index scan:
select distinct SUBOBJECT_NAME from SCOTT.T where SUBOBJECT_NAME IS NOT NULL;
Aparência (em 18c) semelhante a:
select distinct subobject_name from T;
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 715 | 1430 | 436 (3)| 00:00:01 |
| 1 | HASH UNIQUE | | 715 | 1430 | 436 (3)| 00:00:01 |
| 2 | TABLE ACCESS FULL| T | 81426 | 159K| 428 (1)| 00:00:01 |
---------------------------------------------------------------------------
select distinct subobject_name from T where SUBOBJECT_NAME is not null;
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 624 | 1248 | 5 (20)| 00:00:01 |
| 1 | HASH UNIQUE | | 624 | 1248 | 5 (20)| 00:00:01 |
|* 2 | INDEX FAST FULL SCAN| IDX_T_SUBOBJ | 1459 | 2918 | 4 (0)| 00:00:01 |
------------------------------------------------------------------------------------