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 filter
predicate
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