Thiago Arrais
Thiago Arrais

Reputation: 34270

Can PL variables change during SQL queries (why doesn't Oracle optimize my simple query)?

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

Answers (1)

Shannon Severance
Shannon Severance

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

Related Questions