Reputation: 34270
Today I stumbled upon a case where I would readily assume a simple optimization as a no-brainer.
Suppose you have a table called my_table
with a column named my_column
and
you write a stored procedure like this:
procedure my_proc(a_value my_table.my_column%type := null) is
begin
for i in (
select another_column from my_table
where a_value is null or my_column = a_value)
loop
-- do something
end loop;
end;
I've always assumed that the expression a_value is null
is constant for the
sake of the select statement, or any expression consisting purely of PL variables and other constants for that
matter. In other words, it could safely be evaluated prior to the execution of
the query and substituted for a constant. In this code, for example, when
a_value
is not passed, the query would be equivalent to
select another_column from my_table
Conversely, when the value is passed, the query would be equivalent to
select another_column from my_table
where my_column = a_value
For my surprise, this simple optimization is not made. The a_value is null
expression seems to get executed for every record on the table and, with a
sufficiently large table, the difference is noticeable even without any special
tools. I am using version 10 and would regard this optimization as a 2.0 or 3.0
feature that would have been done a long time ago.
There must be some reason for this, obviously. Maybe my assumption that PL variables are constants to the eyes of the SQL query is not true. Maybe PL variables can change during the execution of SQL queries. Do you know of any such case?
Upvotes: 3
Views: 281
Reputation: 18410
When Oracle needs to compile and optimize the SQL query it must create a query plan that will work no matter what the values of the bind variables will be, because that plan can be reused with the same query but different values later.
In the query, select another_column from my_table where a_value is null or my_column = a_value
a_value is the bind variable. The query will only hard parsed into a query plan once. It can not collapse to select another_column from my_table
since the next call to the stored procedure may pass in a non-null a_value.
EDIT Adding an example.
Ask Tom has a posting that deals with a more complicated version of this issue. Based on his answer.
Sample data:
create table my_table(my_column varchar2(10) null
, another_column varchar2(10) null)
/
insert into my_table values('1', 'a');
insert into my_table values('2', 'b');
insert into my_table values('3', 'c');
insert into my_table values('4', 'd');
insert into my_table values('5', 'e');
insert into my_table values('6', 'f');
insert into my_table values('7', 'g');
insert into my_table values('8', 'h');
insert into my_table values('9', 'i');
insert into my_table values('10', 'j');
commit;
The procedure:
create or replace procedure my_proc(p_value in my_table.my_column%TYPE default null) is
type l_rec_type is record(another_column my_table.another_column%TYPE);
l_sql varchar2(32767) := 'select another_column from my_table where ';
l_cursor sys_refcursor;
l_record l_rec_type;
begin
if p_value is null then
l_sql := l_sql || '(1=1 or :my_column is null)';
else
l_sql := l_sql || '(my_column = :my_column)';
end if;
open l_cursor for l_sql using p_value;
loop
fetch l_cursor into l_record;
exit when l_cursor%NOTFOUND;
-- do something
dbms_output.put_line(l_record.another_column);
end loop;
close l_cursor;
end my_proc;
/
Watch it run:
SQL> exec my_proc()
a
b
c
d
e
f
g
h
i
j
PL/SQL procedure successfully completed.
SQL> exec my_proc('2')
b
PL/SQL procedure successfully completed.
Upvotes: 6