Как использовать индекс на основе функций для столбца, который содержит значения NULL в Oracle 10+? - PullRequest
5 голосов
/ 07 октября 2008

Допустим, у вас есть таблица в Oracle:

CREATE TABLE person (
  id NUMBER PRIMARY KEY,
  given_names VARCHAR2(50),
  surname VARCHAR2(50)
);

с этими функциональными индексами:

CREATE INDEX idx_person_upper_given_names ON person (UPPER(given_names));
CREATE INDEX idx_person_upper_last_name ON person (UPPER(last_name));

Теперь, для names_names нет значений NULL, но ради аргумента last_name имеет. Если я сделаю это:

SELECT * FROM person WHERE UPPER(given_names) LIKE 'P%'

план объяснения говорит мне, что он использует индекс, но измените его на:

SELECT * FROM person WHERE UPPER(last_name) LIKE 'P%'

это не так. Документы Oracle говорят, что использование индекса на основе функций будет использоваться только при выполнении нескольких условий, одно из которых - убедиться, что значения NULL отсутствуют, поскольку они не проиндексированы.

Я пробовал эти запросы:

SELECT * FROM person WHERE UPPER(last_name) LIKE 'P%' AND UPPER(last_name) IS NOT NULL

и

SELECT * FROM person WHERE UPPER(last_name) LIKE 'P%' AND last_name IS NOT NULL

В последнем случае я даже добавил индекс для last_name, но независимо от того, что я пробую, он использует полное сканирование таблицы. Предполагая, что я не могу избавиться от значений NULL, как мне заставить этот запрос использовать индекс UPPER (last_name)?

Ответы [ 5 ]

7 голосов
/ 07 октября 2008

Можно использовать индекс, хотя оптимизатор, возможно, решил не использовать его для вашего конкретного примера:

SQL> create table my_objects
  2  as select object_id, object_name
  3  from all_objects;

Table created.

SQL> select count(*) from my_objects;
  2  /

  COUNT(*)
----------
     83783


SQL> alter table my_objects modify object_name null;

Table altered.

SQL> update my_objects
  2  set object_name=null
  3  where object_name like 'T%';

1305 rows updated.

SQL> create index my_objects_name on my_objects (lower(object_name));

Index created.

SQL> set autotrace traceonly

SQL> select * from my_objects
  2  where lower(object_name) like 'emp%';

29 rows selected.


Execution Plan
----------------------------------------------------------

------------------------------------------------------------------------------------
| Id  | Operation                   | Name            | Rows  | Bytes | Cost (%CPU)|
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                 |    17 |   510 |   355   (1)|
|   1 |  TABLE ACCESS BY INDEX ROWID| MY_OBJECTS      |    17 |   510 |   355   (1)|
|*  2 |   INDEX RANGE SCAN          | MY_OBJECTS_NAME |   671 |       |     6   (0)|
------------------------------------------------------------------------------------

В прочитанной вами документации, по-видимому, указывалось, что, как и любой другой индекс, все нулевые ключи не сохраняются в индексе.

2 голосов
/ 07 октября 2008

В вашем примере вы создали один и тот же индекс дважды - это даст ошибку, поэтому я предполагаю, что это была ошибка при вставке, а не фактический код, который вы пробовали.

Я попробовал это с

CREATE INDEX idx_person_upper_surname ON person (UPPER(surname));

SELECT * FROM person WHERE UPPER(surname) LIKE 'P%';

и получен ожидаемый план запроса:

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=67)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'PERSON' (TABLE) (Cost=1
          Card=1 Bytes=67)

   2    1     INDEX (RANGE SCAN) OF 'IDX_PERSON_UPPER_SURNAME' (INDEX)
           (Cost=1 Card=1)

Чтобы ответить на ваш вопрос, да, это должно работать. Попробуйте дважды проверить, правильно ли создан второй индекс.

Также попробуйте явную подсказку:

SELECT /*+INDEX(PERSON IDX_PERSON_UPPER_SURNAME)*/ * 
FROM person 
WHERE UPPER(surname) LIKE 'P%';

Если это работает, но только с подсказкой, то это, скорее всего, связано с ошибкой статистики CBO или параметрами инициализации CBO.

0 голосов
/ 07 октября 2008

Oracle по-прежнему будет использовать индексы на основе функций со столбцами, которые содержат ноль - я думаю, что вы неверно истолковали документацию.

Вам нужно добавить nvl в индекс функции, если вы хотите проверить это.

Что-то вроде ...

create index idx_person_upper_surname on person (nvl(upper(surname),'N/A'));

Затем вы можете запросить, используя индекс с

select * from person where nvl(upper(surname),'N/A') = 'PIERPOINT'

Хотя, все немного некрасиво. Поскольку у большинства людей есть фамилии, возможно, уместно указать «не ноль»: -).

0 голосов
/ 07 октября 2008

Вы можете обойти проблему неиндексации нулевых значений в этой или других ситуациях, также индексируя на основе литерального значения:

CREATE INDEX idx_person_upper_surname ON person (UPPER(surname),0);

Это позволяет использовать индекс для таких запросов, как:

Select *
From   person
Where  UPPER(surname) is null;

Этот запрос обычно не использует индекс, за исключением индексов растрового изображения или индексов, включающих ненулевой вещественный столбец, отличный от фамилии.

0 голосов
/ 07 октября 2008

Вы уверены, что хотите использовать индекс? Полное сканирование таблицы не плохо. В зависимости от размера таблицы может быть более эффективно выполнить сканирование таблицы, чем использовать индекс. Это также зависит от плотности и распределения данных, поэтому статистика собирается. Оптимизатору на основе затрат обычно можно доверять, чтобы сделать правильный выбор. Если у вас нет конкретной проблемы с производительностью, я бы не стал сильно беспокоиться об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...