You are on page 1of 5

BY TOM KYTE

t e c h n o l o g y ASK TOM
O R A C L E M A G A Z I N E J U LY / A U G U S T 2 0 0 9 1
t is time to do a flashback query
of sorts and revisit a column, On
Rescue Analytics and Popularity,
from almost three years ago (Oracle
Magazine, November/December 2006,
otn.oracle.com/oramag/oracle/06-nov/
o66asktom.html). At that time, one
answer from this column was the most
read answer on AskTom (asktom.oracle
.com), and it still holds that record to
this day. Heres the original question:
I want to declare multiple cursors based
on the values passed through a procedure, and
only the WHERE conditions of the cursors will
change. The body of the procedure is the same
for all the cursors otherwise.
When I first answered this question
(on AskTom in 2000!), I recommended
using an application context to bind
inputs. The technique was as follows:
if ( p_ename is NOT NULL )
then
dbms_session.set_context
( MY_CTX, ENAME,
%||upper(p_ename)||%);
l_query := l_query ||
and ename like
sys_context
( MY_CTX,
ENAME ) ;
end if;
In this case, the code was checking
to see if the P_ENAME parameter had a
value. If it did, the code would bind
%||upper(p_ename)||%
into the query, using an application
context. This was a two-step process:
first put the value into the application
context and then reference it in the
WHERE clause of the query.
The goal of the procedure was to
construct a query based on the inputs
and return a ref cursor to the client, and
the procedure permitted you to have an
unknown number of bind variables and
use native dynamic SQL.
To use native dynamic SQL,
however, you need to know at compile
time how many bind variables will be
used. And because we were building
the query on the fly, based on how
many things we wanted to bind, that
didnt seem possible or efficient. Hence,
the application context approach.
Well, a friend of mineBryn
Llewellyn, the PL/SQL product
manager at Oraclehad a look at the
implementation as presented and sug-
gested that it was overly complex. He
reminded me about an alternative,
and superior, implementation thats
been used in the field for some years.
It would not only simplify the code
but would also have a positive impact
on performance. By using this new
approachoutlined belowwell avoid
a few function calls and the use of
application contexts altogether. That
last point is important in some envi-
ronments, because to create a context,
you would need the CREATE ANY
CONTEXT privilege, andfor good
reasonthat might be a privilege you
are not accorded.
The superior implementation sug-
gestion was to always code a reference
to every possible bind variable but to
do so in such a way that the optimizer
would remove the bind variable for us
if we werent going to actually use it
in that particular execution of the SQL
statement. So now, because we have
a fixed number of binds (equal to the
number of parameters for the proce-
dure that builds the SQL), we can use
native dynamic SQL and get the best-
possible plan.
Im not going to present the
old application context code in its
entiretyyou can see that on the Oracle
Magazine Web site. What I will present
here is the new code and a comparison
of the queries generated by the old and
the new code.
First, the suggested code modifica-
tion was to build a predicate for each
possible bind input that would look
like either
1. WHERE column = :bind_variable
or
2. WHERE (1=1 or :bind_variable is null)
We would use #1 when the bind
variable was to be used to constrain
the result set and #2 when the bind
variable wasnt to be supplied. The
optimizer would see 1=1 or <something
else> and automatically know that it
was true, so it would just optimize this
predicate away.
The goal for the new code is the
same as for the old code, so well take
the old approach and recode it, using
the new approach. We want to code
a procedure that will accept as many
as three inputs (ENAME, HIREDATE,
and SAL) and dynamically construct
a query against the EMP table, using
those inputs. If we pass in ENAME,
we want the ENAME column in every
record to be like the uppercase value
of the input. If we pass in HIREDATE,
we want everyone hired after that date.
Likewise, if we pass in SAL, we want
everyone that makes more than the
supplied value. If we send in two or
three arguments to this procedure, we
would like the filters we build to be
connected (by AND).
The specification of MY_NEW_
PROCEDURE is shown in Listing 1. In
MY_NEW_PROCEDURE, we accept our
three inputs and set up some local vari-
ables. L_CURSOR is the ref cursor we
would return to the client application,
On Popularity and Natural Selection
Our technologist optimizes away binds and promotes more-selective queries.
2 J U LY / A U G U S T 2 0 0 9 O R A C L E . C O M / O R A C L E M A G A Z I N E
ASK TOM
but because this is a quick demonstra-
tion, we will not be returning this value.
Instead, we will process the value in the
procedure itself to see the results and use
L_REC to fetch into. The L_QUERY vari-
able is, as its name implies, a string to
hold the SQL statement we generate.
The code for building the query is
very simple. I presented the code for
binding P_ENAME, using an applica-
tion context; here is the new approach:
14 if ( p_ename is NOT NULL )
15 then
16 l_query := l_query ||
17 where ename like
18 %||:p_ename||% ;
19 else
20 l_query := l_query ||
21 where (1=1 or
22 :p_ename is null) ;
23 end if;
Note that we always bind :P_ENAME
into the query now. This is keyit
enables us to use native dynamic SQL,
because well have a static set of bind
variables known at compile time.
The rest of the code for binding the
other two columns is equally simple:
24 if ( p_hiredate is NOT NULL )
25 then
26 l_query := l_query ||
27 and hiredate > :p_hiredate ;
28 else
29 l_query := l_query ||
30 and (1=1 or
31 :p_hiredate is null) ;
32 end if;
33
34 if ( p_sal is NOT NULL )
35 then
36 l_query := l_query ||
37 and sal > :p_sal ;
38 else
39 l_query := l_query ||
40 and (1=1 or
41 :p_sal is null) ;
42 end if;
The main body of the demonstration
procedure is then as follows:
43 dbms_output.put_line
44 ( l_query );
45
46 open l_cursor
47 for l_query
48 using p_ename,
49 p_hiredate,
50 p_sal;
51
52 loop
53 fetch l_cursor
54 into l_rec;
55 exit when
56 l_cursor%notfound;
57
select *
from emp
where (1=1 or
:p_ename is null)
and hiredate >
:p_hiredate
and (1=1 or
:p_sal is null)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 87 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
----------------------------------------------------------------------------------------------------------------------
1 - filter(HIREDATE>:P_HIREDATE)
Note
-------------
dynamic sampling used for this statement
codeLISTING 3: A query and minimal predicate information
SQL> create or replace
2 procedure my_new_procedure
3 ( p_ename in varchar2 default NULL,
4 p_hiredate in date default NULL,
5 p_sal in number default NULL)
6 as
7 l_cursor sys_refcursor;
8 l_query varchar2(512)
9 default select * from emp;
10
11 cursor l_template is select * from emp;
12 l_rec l_template%rowtype;
13 begin
codeLISTING 1: Specification of my_new_procedure
select *
from emp
where (1=1 or
:p_ename is null)
and (1=1 or
:p_hiredate is null)
and (1=1 or
:p_sal is null)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
| 1 | TABLE ACCESS FULL| EMP | 14 | 1218 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Note
-------------
dynamic sampling used for this statement
codeLISTING 2: New query and plan (with no predicate information)
O R A C L E M A G A Z I N E J U LY / A U G U S T 2 0 0 9 3
58 dbms_output.put_line
59 ( l_rec.ename || , ||
60 l_rec.hiredate || , ||
61 l_rec.sal );
62 end loop;
63 close l_cursor;
Finally, we simply open the cursor,
and in real life, that would be the end
of it (we would return that to the client
and then fetch and print out each row).
To show what the optimizer is doing
with our query, Ive added a little diag-
nostic output at the end:
64 for x in
65 (select plan_table_output
66 from table
67 (dbms_xplan.display_cursor)
68 )
69 loop
70 dbms_output.put_line
71 ( x.PLAN_TABLE_OUTPUT );
72 end loop;
73
74 end;
75 /
Procedure created.
Right after we run the query, we can
get the actual plan used, by invoking
DBMS_XPLAN.DISPLAY_CURSOR (in
Oracle Database 10g and above only).
Well do that to verify that the optimizer
removes the predicate when it is not
relevant, confirming that this is a very
efficient approach.
Now suppose we run MY_NEW_
PROCEDURE with no inputs whatso-
ever. Using the old code, the resulting
query is
select *
from emp
where 1 = 1
Using the new approach, in contrast,
our query is
select *
from emp
where (1=1 or
:p_ename is null)
and (1=1 or
:p_hiredate is null)
and (1=1 or
:p_sal is null)
At first you might look at the queries
and say, The new way looks like a
much more complex query for the data-
base. However, see what the optimizer
did with the new query in Listing 2.
We can see that there is no Predicate
Information section, which is because,
from the optimizers standpoint, our
new query was simply SELECT * FROM
EMPthe entire WHERE clause was
optimized away.
Now suppose we want to apply
a predicate against the HIREDATE
column. The old approach would have
generated this query:
select *
from emp
where 1 = 1
and hiredate >
to_date(
sys_context( MY_CTX,
HIREDATE ),
yyyymmddhh24miss)
We had to cast the application context
to a date (all context values are strings
no other type is available). When we
loaded the context, we had to pick a
stable date format to encode the date into
a string, and when we referenced it in
the query, we had to decode it back out.
The new query lacks that complexityit
doesnt have those function calls:
select *
from emp
where (1=1 or
:p_ename is null)
and hiredate >
:p_hiredate
and (1=1 or
:p_sal is null)
SQL> create table t ( x int, y int );
Table created.
SQL> create or replace
2 procedure bad_proc
3 as
4 cursor c
5 is
6 select *
7 from t;
8 begin
9 null;
10 end;
11 /
Procedure created.
SQL> create or replace
2 procedure good_proc
3 as
4 cursor c
5 is
6 select x,y
7 from t;
8 begin
9 null;
10 end;
11 /
Procedure created.
SQL> select object_name, status
2 from user_objects
3 where object_name in
4 (BAD_PROC, GOOD_PROC)
5 order by object_name;
OBJECT_NAME STATUS
----------------------------------------------------------------------- -----------------
BAD_PROC VALID
GOOD_PROC VALID
codeLISTING 4: bad_proc, good_proc
4 J U LY / A U G U S T 2 0 0 9 O R A C L E . C O M / O R A C L E M A G A Z I N E
ASK TOM
Because we are binding a date, no
conversions are necessary. The opti-
mizergiven this querycomes up
with the plan in Listing 3.
Note that the Predicate Information
in the plan is reduced to just the
relevant bit for us (HIREDATE>:P_
HIREDATE). We can bind all three
columns but have the optimizer throw
away the bits that are not necessary.
It is always nice to be exposed to or
reminded of a superior technique, and I
now definitely prefer this approach over
the application context. I have updated
The Most Popular Answer Ever with a
link to this column.
REASON #13134213 NOT TO USE SELECT *
There are many reasons to avoid using
SELECT * in your code, and perfor-
mance is a major one. SELECT * will
typically force a query to access a table,
whereas SELECT <just the columns you
need> may well be able to avoid the
table altogether (because it might be
able to get the answer from the index).
Code maintenance is another reason
to avoid SELECT *. If someone adds
a column to a tableand your code
is not expecting thatyour code may
well break.
Oracle Database 11g adds to the very
long list yet another reason to avoid
SELECT *. In that release, the database
dependency mechanism was extended
down to the column level, so if your
procedures are constructed with explicit
references to just the columns you need,
you will find your code being inval-
idatedand therefore needing to be
recompiledmuch less often. Consider
the small example in Listing 4.
Right now both the BAD_PROC and
GOOD_PROC procedures are equiva-
lent: they do the same thing, and they
are both valid. However, BAD_PROC is
dependent on the entire table T struc-
ture whereas GOOD_PROC is depen-
dent only on X and Y in table T.
If we modify table T:
SQL> alter table t add z number;
Table altered.
SQL> select object_name, status
2 from user_objects
3 where object_name in
4 (BAD_PROC, GOOD_PROC)
5 order by object_name;
OBJECT_NAME STATUS
------------------------------- ------------------
BAD_PROC INVALID
GOOD_PROC VALID
you will be able to see that the STATUS
of BAD_PROCwhich included
SELECT *is now INVALID, and it
needs to be recompiled. GOOD_PROC,
SQL> create or replace
2 procedure p1
3 as
4 begin
5 dbms_output.put_line
6 ( hello WORLD );
7 end;
8 /
Procedure created.
SQL> select object_name, status,
2 to_char( last_ddl_time,
3 hh24:mi:ss) ddl_time
4 from user_objects
5 where object_name in
6 (P1, P2)
7 order by object_name;
OBJECT_NAME STATUS DDL_TIME
-------------------------------------------------------------- ----------------- -------------------------
P1 VALID 08:24:41
P2 VALID 08:24:40
codeLISTING 6: Modifying P1, but P1 and P2 procedures are still valid
SQL> create or replace
2 procedure p1
3 as
4 begin
5 dbms_output.put_line
6 ( hello );
7 end;
8 /
Procedure created.
SQL> create or replace
2 procedure p2
3 as
4 begin
5 p1;
6 end;
7 /
Procedure created.
SQL> select object_name, status,
2 to_char( last_ddl_time,
3 hh24:mi:ss) ddl_time
4 from user_objects
5 where object_name in
6 (P1, P2)
7 order by object_name;
OBJECT_NAME STATUS DDL_TIME
-------------------------------------------------------------- ----------------- -------------------------
P1 VALID 08:24:40
P2 VALID 08:24:40
codeLISTING 5: Creating P1 and P2
overs
on the other hand, remains VALID.
Youll find that application upgrades and
maintenance tasks greatly benefit from
this new column dependency capability,
provided that the developers wrote the
code correctly in the first place!
Another new dependency feature
in Oracle Database 11g is that its pos-
sible to recompile procedures without
invalidating dependent code, as long as
the signature of the procedure does not
change. That is, if you do not modify
the list of inputs and outputs of your
procedures, the dependent code will
not need to be recompiled. This enables
you to patch your system with a sig-
nificantly smaller maintenance window.
Consider the procedures in Listing 5.
So, now we have the P1 and P2 pro-
cedures, P2 is dependent on P1, and
both were compiled at about the same
time. If we recompile P1 after modify-
ing the code (fixing a bug, perhaps),
well find the result in Listing 6.
Now, in Oracle Database 10g and
before, P1 would be invalid, but
because we were using Oracle Database
11g and we did not change the sig-
nature of the procedure, we did not
invalidate the dependent code.
Oracle Database 10g added another
optimization for application upgrades:
the compiler checks whether newly
supplied application code differs from
the existing code. If the new code you
CREATE OR REPLACE is the same as
the old codeand the PL/SQL compila-
tion environment settings are the same
as they were when we first compiled
the codewe will skip compilation of
the new code. This feature was added
because in many cases, applications
upgrade themselves by re-creating all
of their code, even if only 10 percent
of it has changed. This new capability
enables us to possibly skip compiling
90 percent of the code. We can easily
see this in the following:
SQL> exec dbms_lock.sleep(2)
PL/SQL procedure successfully completed.
SQL> create or replace
2 procedure p1
3 as
4 begin
5 dbms_output.put_line
6 ( hello WORLD );
7 end;
8 /
Procedure created.
SQL> select object_name, status,
2 to_char( last_ddl_time,
3 hh24:mi:ss) ddl_time
4 from user_objects
5 where object_name in
6 (P1, P2)
7 order by object_name;
OBJECT_NAME STATUS DDL_TIME
-------------------------------- - ----------------- ---------------------
P1 VALID 08:24:41
P2 VALID 08:24:40
Note how the time stamp (DDL_
TIME) for the P1 procedure did not
change. We waited at least two seconds,
and we know that the time must have
been 8:24:43 or later when we last com-
piled this code, but because we did not
modify it, we did not compile it. This
new feature has caught some people
when they upgraded their applications.
Now suppose that you had a process
for upgrading your application that
performed these steps:
1. Check all code out of source code
control
2. Run CREATE OR REPLACE for
all code
3. Query USER_OBJECTS to verify that
LAST_DDL_TIME was updated
That last step is what has caught
people. Most of their code hadnt actu-
ally changed between upgrades, and
hence most of it didnt have to be
compiled. LAST_DDL_TIME wasnt
modified, so their process thought
something had gone wrong with the
upgrade. But it is easy to modify the
process and reap the benefits of this
improved compilation.
Just for completeness, heres a dem-
onstration of what happens in Oracle
Database 11g if you change the signature
of your stored procedure in any way:
SQL> create or replace
2 procedure p1
3 ( x in number default NULL )
4 as
5 begin
6 dbms_output.put_line
7 ( hello WORLD );
8 end;
9 /
Procedure created.
SQL> select object_name, status,
2 to_char( last_ddl_time,
3 hh24:mi:ss) ddl_time
4 from user_objects
5 where object_name in
6 (P1, P2)
7 order by object_name;
OBJECT_NAME STATUS DDL_TIME
-------------------------------- ------------------ ---------------------
P1 VALID 08:24:45
P2 INVALID 08:24:40
Note that P2 is now invalid, due to
the change in P1s specification. Even
though that change did not really affect
P2, P1s signature was modified, so the
dependent code was invalidated and
will be recompiled.
Tom Kyte is a database evangelist in Oracles Server
Technologies division and has worked for Oracle since
1993. He is the author of Expert Oracle Database
Architecture: 9i and 10g Programming Techniques and
Solutions (Apress, 2005) and Effective Oracle by Design
(Oracle Press, 2003), among others.
ASK Tom
Tom Kyte answers your most difficult technology ques-
tions. Highlights from that forum appear in this column.
asktom.oracle.com
READ more about
declaring multiple cursors
(a.k.a. the most popular answer ever)
asktom.oracle.com/~tkyte/cursor.html
Oracle Database 11g
otn.oracle.com/products/database/oracle11g
READ more Tom
Expert Oracle Database Architecture: 9i and 10g
Programming Techniques and Solutions
amazon.com/exec/obidos/tg/detail/-/1590595300/
tkyte.blogspot.com
DOWNLOAD
Oracle Database 11g
otn.oracle.com/software/products/database
nextSTEPS

You might also like