Home > Net >  LNNVL to replace NOT IN?
LNNVL to replace NOT IN?

Time:10-22

This one was a bit difficult to search, so I'm asking here (where I saw questions regarding lnnvl()).

I have a query that looks like:

SELECT *
FROM foo
WHERE foo.bar NOT IN ('X', 'Y')
   OR foo.bar IS NULL;

Moments ago I learned about lnnvl() and I was able to do the same thing with:

SELECT *
FROM foo
WHERE lnnvl(foo.bar = 'X')
  AND lnnvl(foo.bar = 'Y');

That is fantastic, but not very scalable when checking a larger set. Is there a cleaner way that any of you folks know of? In the past, I've done something like:

SELECT *
FROM foo
WHERE nvl(foo.bar, ' ') NOT IN ('X', 'Y')

I appreciate any insights!

CodePudding user response:

Your last option is fine and leads to following filterpredicate

filter(NVL("FOO"."BAR",' ')<>'X' AND NVL("FOO"."BAR",' ')<>'Y')

I do not see other possibility as the predicate below leads to an exception

WHERE lnnvl(foo.bar IN ('X', 'Y')); 
ORA-13207: incorrect use of the [LNNVL] operator

If your table is very large and you want to avoid full table scan you may even define a function based index as

create index idx on foo(nvl(bar,' ')); 

This will prevent the full table scan, but unfortunately the index can't be used as usual - as you check for the inequity <>.

So the result is a full index scan i.e. you go through the whole index and filter the not matching keys, accesing the table only for the right keys.

This could be meaningfull if the table is large and the result set is small.

Execution Plan

--------------------------------------------------------------------------------------------
| Id  | Operation                           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |      |   250 |   489K|   269   (1)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| FOO  |   250 |   489K|   269   (1)| 00:00:01 |
|*  2 |   INDEX FULL SCAN                   | IDX  |   250 |       |   185   (2)| 00:00:01 |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter(NVL("BAR",' ')<>'X' AND NVL("BAR",' ')<>'Y')

BTW

you can't use index for the predicate lnnvl(bar = 'X') which leads obvious to full table scan with the predicate filter(LNNVL("BAR"='X'))

Anyway you may re-formulate the predicate to

WHERE 
case when lnnvl(bar = 'X') then 1 end = 1

and define FBI index

create index idx2 on foo( case when lnnvl(bar = 'X') then 1 end ); 

which leads to a nice index range scan

you may even use and e.g.

where case when lnnvl(bar = 'X') and lnnvl(bar = 'Y') then 1 end = 1 
  • Related