2008-10-07 7 views
5

Lets dire que vous avez une table Oracle:Comment utiliser un index basé sur une fonction sur une colonne contenant des valeurs NULL dans Oracle 10+?

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

avec ces fonctions basée sur les indices:

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

Maintenant, given_names n'a pas de valeur NULL mais pour le last_name cause de l'argument fait. Si je fais ceci:

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

le plan me dit expliquer son utilisation de l'index, mais changer pour:

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

il ne fonctionne pas. Les documents Oracle indiquent que l'utilisation de l'index basé sur la fonction ne sera utilisée que lorsque plusieurs conditions sont remplies, dont l'une est de s'assurer qu'il n'y a pas de valeurs NULL puisqu'elles ne sont pas indexées.

J'ai essayé ces requêtes:

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

et

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

Dans ce dernier cas, j'ai même ajouté un index sur last_name mais peu importe ce que j'essayer utilise un scan de table. En supposant que je ne peux pas me débarrasser des valeurs NULL, comment puis-je obtenir cette requête pour utiliser l'index sur UPPER (last_name)?

+0

Combien de lignes avez-vous réellement dans la table? Pouvez-vous poster le plan d'explication pour l'analyse complète de la table et aussi quand il choisit d'utiliser l'index (vous devrez peut-être l'indiquer ou changer la colonne pour NON NULL pour les besoins de l'exercice). –

Répondre

7

L'indice peut être utilisé, bien que l'optimisateur peut avoir choisi de ne pas l'utiliser pour votre exemple particulier:

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)| 
------------------------------------------------------------------------------------ 

La documentation vous lisez probablement désignez a été que, comme tout autre indice, tout- Les clés NULL ne sont pas stockées dans l'index.

2

Dans votre exemple, vous avez créé le même index deux fois - cela donnerait une erreur, donc je suppose que c'était une erreur de coller, pas le code que vous avez essayé.

Je l'ai essayé avec

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

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

et produit le plan de requête prévu:

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) 

Pour répondre à votre question, oui il devrait fonctionner. Essayez de vérifier que le deuxième index a été créé correctement.

Essayez aussi un indice explicite:

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

Si cela fonctionne, mais seulement avec le soupçon, il est probablement lié à des statistiques CBO qui ont mal tourné, ou les paramètres d'initialisation liés CBO.

+0

Avez-vous mis des valeurs nulles dans la table et obtenu ce plan de requête? – cletus

0

Etes-vous sûr de vouloir utiliser l'index? Les analyses de table complètes ne sont pas mauvaises. En fonction de la taille de la table, il peut être plus efficace d'effectuer une analyse de table que d'utiliser un index. Cela dépend aussi de la densité et de la distribution des données, c'est pourquoi les statistiques sont collectées. On peut généralement faire confiance à l'optimiseur basé sur les coûts pour faire le bon choix. Sauf si vous avez un problème de performance spécifique, je ne m'inquiéterais pas trop à ce sujet.

0

Vous pouvez contourner le problème des valeurs nulles étant non indexées dans ce ou d'autres situations par l'indexation également basée sur une valeur littérale:

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

Cela vous permet d'utiliser l'index pour des requêtes telles:

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

Cette requête n'utiliserait normalement pas d'index, à l'exception des index bitmap ou des index incluant une colonne réelle non cumulable autre que le nom de famille.

+0

David, quel type de requête pourrait utiliser un tel index? –

+1

Toute requête utilisant UPPER (nom de famille). L'astuce de David garantit que les valeurs nulles de UPPER (nom de famille) sont indexées. Oracle n'indexe pas si toutes les valeurs sont nulles. La valeur littérale de 0 garantit que cela n'arrive jamais. –

0

Oracle utilisera toujours un index basé sur des fonctions avec des colonnes contenant null - je pense que vous avez mal interprété la documentation.

Vous devez placer un nvl dans l'index de fonction si vous voulez vérifier ceci.

Quelque chose comme ...

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

Vous pouvez ensuite en utilisant requête l'index avec

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

Bien que, tous un peu laid. Puisque la plupart des gens ont des noms de famille, peut-être qu'un "non nul" est approprié :-).

Questions connexes