3

I have a common problem with ORACLE in following example code:

create or replace procedure usp_test
(
  p_customerId number,
  p_eventTypeId number,
  p_out OUT SYS_REFCURSOR
)
as
begin

  open p_out for
    select e.Id from eventstable e
    where 
      (p_customerId is null or e.CustomerId = p_customerId)
      and
      (p_eventTypeId is null or e.EventTypeId = p_eventTypeId)
    order by Id asc;

end usp_test;

The "OR" in "(p_customerId is null or e.CustomerId = p_customerId)" kills procedure performance, because optimizer will not use index (i hope for index seek) on "CustomerId" column optimally, resulting in scan instead of seek. Index on "CustomerId" has plenty of distinct values.

When working with MSSQL 2008 R2 (latest SP) or MSSQL 2012 i can hint the query with "option(recompile)" which will:

  1. Recompile just this query
  2. Resolve values for all variables (they are known after sproc is called)
  3. Replace all resolved variables with constants and eliminate constant predicate parts

For example: if i pass p_customerId = 1000, then "1000 is null" expression will always be false, so optimizer will ignore it. This will add some CPU overhead, but it is used mostly for rarely called massive reports procedures, so no problems here.

Is there any way to do that in Oracle? Dynamic-SQL is not an option.

Adds

Same procedure just without "p_customerId is null" and "p_eventTypeId is null" runs for ~0.041 seconds, while the upper one runs for ~0.448 seconds (i have ~5.000.000 rows).

infideltfo
  • 95
  • 1
  • 6
  • But if you pass the procedure 1,000 then you _want_ it to scan the table... your procedure looks fine and it should use the index. How are your statistics? – Ben Dec 26 '12 at 18:36
  • It's not good practice to prefix the parameter names with p_. Just use the appropriate name, and when you use the variable name in a SQL statement prefix it with the procedure or function name. eg. "usp_test.customerid". – David Aldridge Dec 26 '12 at 18:38
  • 1
    Dynamic SQL is not only an actual option, but quite possibly the best one as it would give the optimiser the best possible chance of providing you with the optimal query execution plan. I don't know why you'd choose not to adopt it. – David Aldridge Dec 26 '12 at 18:43
  • In case of "p_" naming this just an example sproc with random name. About scan: i am not sure about what ORACLE exactly do here (scan nonclustered index, or scan clustered index) because it seems that i cannot look for execution plan for PL/SQL. But in comparison with same procedure without "param is null" part it runs 10 or more times slower. – infideltfo Dec 26 '12 at 19:01
  • I am new to ORACLE, but in MSSQL i do not use dynamic sql because of the absence of static code check. Refactoring is also problematic. – infideltfo Dec 26 '12 at 19:17
  • About a scan: no, i do not expect an index scan because selectivity is very low. I have a plenty of distinct values in it. So i expect seek. But in case of both Oracle and MSSQL (without hints) i get scan. (it uses correct index, but unfortunately scans it). – infideltfo Dec 26 '12 at 19:30
  • 3
    @DavidAldridge - "It's not good practice to prefix the parameter names with p_". Really? Using prefixes to indicate the declaration scope of variables is incredibly common (although some PL/SQL standards prefer `i_`, `o_` and `io_` indicating mode instead). – APC Dec 26 '12 at 19:37
  • @APC yep I agree that it's common, but popular != best practice (eg. buffer cache hit ratio, explicit cursors, unbound variables). Why doesn't Oracle Corp follow one of these popular practices? They keep their APIs clean by avoiding them, and prefixing with the block label or procedure/function name is the most robust way to resolve namespace clashes between SQL and PL/SQL. – David Aldridge Dec 26 '12 at 22:37

2 Answers2

0

One column index can't help as it's not stored in index definition. Is creating index on (customer id, event id, id ) allowed? This way all needed columns are in index...

igr
  • 3,409
  • 1
  • 20
  • 25
  • I have index on CustomerId, EventTypeId and on both CustomerId and EventTypeId. That does not matter. Procedure with "p_customerId is null" runs 10 or more times slower than same procedure without such predicate. – infideltfo Dec 26 '12 at 19:09
0
CREATE INDEX IX_YOURNAME1 ON eventstable (NVL(p_customerId, 'x'));
CREATE INDEX IX_YOURNAME2 ON eventstable (NVL(p_eventTypeId, 'x'));

create or replace procedure usp_test
(
  p_customerId number,
  p_eventTypeId number,
  p_out OUT SYS_REFCURSOR
)
as
begin

  open p_out for
    select e.Id from eventstable e
    where 
        (NVL(p_customerId, 'x') = e.CustomerId OR NVL(p_customerId, 'x') = 'x')
    AND (NVL(p_eventTypeId, 'x') = e.EventTypeId OR NVL(p_eventTypeId, 'x') = 'x')
    order by Id asc;
end usp_test;
Sebas
  • 21,192
  • 9
  • 55
  • 109
  • Wow. It works fine. Interesting solution, thanks! (i have to replace NVL(p_customerId, 'x') with NVL(p_customerId, -1), but idea is ok) – infideltfo Dec 26 '12 at 19:39
  • you're welcome! The indices I suggested were for a general answer, I guess you could as well provide some fine tuning to your model if you already have indices on the table. – Sebas Dec 26 '12 at 19:46
  • If i understand right, NVL(CustomerId, -1) will just replace all nulls with -1 on index? Won't it increase index weight and disrupt selectivity? – infideltfo Dec 26 '12 at 19:48
  • This fix boosts query performance more than twice: from 0.500 to 0.200 seconds. But it still far from 0.041 seconds for a query without "OR" or "NVL". – infideltfo Dec 26 '12 at 20:31
  • oracle will index the result of NVL(customerid, -1) so it can be used for filtering. For your performance problem if you remove a filter the choice of indexes is more obvious, check whether the explain plan fits or not – Sebas Dec 26 '12 at 21:02
  • 1
    This so much more messy than a method based on dynamic sql, and almost certainly doesn't perform as well. – David Aldridge Dec 26 '12 at 22:51
  • dynamic sql doesn't have anything to do with querty optimization, However, it bears an inherent performance drop due to the interpretetation times, fortunately "counter-parted" by a proper script pre-compilation that would indeed result into a global script optimization. (knowing whether or not p_customerId si null before hand would help) – Sebas Dec 27 '12 at 03:04
  • I should also add that this optimization could be performed without dynamic sql, just opening 2 different cursors depending on the values of the parameters. Please avoid dynamic sql unless extreme necessity. Writting comments after clubbing is hard. Cheers. – Sebas Dec 27 '12 at 03:07