Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Andrew.

Asked: July 19, 2007 - 4:04 pm UTC

Last updated: September 12, 2007 - 10:06 am UTC

Version: 9.2.0.4

Viewed 1000+ times

You Asked

Tom,
I do part-time development and part-time DBA work, a hybrid of sorts. I have been noticing more and more performance issues that people are having when using plsql function calls from within SQL. For example:

select col1, col2, fnc1(col3, col4) colnew
  from <table>
 where col1 = fnc1(col3, col4)
  ...



I completely agree with the encapsulation of business logic in the plsql code, but when used within SQL, there is no way for the optimizer to determine the impact the function will have on the query. If the pseudo query above returns 100K rows, fnc1 will get executed 200K times. (Yes we might alleviate that with in-line views, etc). But effectively we have a nested loops processing where a "FTS" of the function might be better. Granted, the CBO doesn't know what the function does so it is nearly impossible to not process the function calls one by one.

Also if the functions use plsql tables (collections) to "cache" results in packages, those plsql tables can get very large and VERY inefficient. We had one query that went on for 1.5 hours that was tuned to about 8 minutes once the plsql functions were refactored.

It is just something I am noticing and I am looking for a way to make it easier to integrate PLSQL function calls within SQL.

Your thoughts are appreciated.
Thanks.
Andrew

and Tom said...

My thoughts have always been the same -

1) if you can do it in all SQL - do it, never write code unless you are forced to.
2) if you absolutely cannot do it in sql, write as little plsql as you can (and understand how it works - scalar subquery caching for example can be of great benefit in your case sometimes)
3) if you cannot do it in plsql (because it requires doing something plsql is not capable of doing - which is very very little today actually) - write as little java as you can


so, I'm always looking at things like "FNC1" and rewriting them using A JOIN, or a scalar subquery.


Rating

  (6 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

For functions in predicates....

A reader, July 22, 2007 - 7:26 pm UTC

"select col1, col2, fnc1(col3, col4) colnew"
...
"there is no way for the optimizer to determine the impact the function will have on the query. "
For a function in the SELECT list, the optimizer wouldn't be interested. Since it must be executed the same number of times regardless of the plan, it doesn't affect the choice of plan.

If it is used as a predicate, there is
ASSOCIATE STATISTICS WITH FUNCTIONS myFunction DEFAULT COST ...
http://download.oracle.com/docs/cd/B14117_01/server.101/b10759/statements_4006.htm#sthref4268
and (more complicated) ODCIStatsFunctionCost
http://download.oracle.com/docs/cd/B14117_01/appdev.101/b10800/dciextopt.htm#sthref413

More info please

A reader, July 23, 2007 - 7:54 am UTC

Hi Tom,
Would you please elaborate on the Associate Statistics statement? Where/how would you use it? And the benefits?
Thanks

Encapsulation

Andrew Markiewicz, July 23, 2007 - 3:01 pm UTC

I agree with trying to as much as possible in SQL. But plsql is heavily used to encapsulate business logic and creating APIs. Then other programs need to interact via that API, so rewriting as a join is not allowed. Also, the idea of encapsulating what a function is doing would be lost if the join had to be duplicated in several places.
Some of the main Pros of implementing as a function are to make that business process consistent across every program that uses it and allowing the function to be refactored without changes to the calling programs/SQL.

I was not familiar with scalar subqueries. It sounds promising, but should they not be required to be deterministic? Within a single SQL statement I suppose we would have read consistency with regard to other sessions DML, but it might be possible to get incorrect results due to result caching if that function does processing that is not only based on table data (plsql package variables, etc). A reviewer on this link describes that situation and you responded.

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:40299712621507

Andrew
Tom Kyte
July 24, 2007 - 9:08 am UTC

... so rewriting as a join is not allowed ...

bollocks


views - put it into a view.


And your comment on scalar subqueries is INTERESTING - since they are read consistent - but your PLSQL FUNCTIONS ARE NOT!!!! It is possible to get incorrect results because of yours FUNCTIONS - not the other way around!!

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:228182900346230020


Andrew Markiewicz, July 24, 2007 - 10:28 am UTC

Regarding the read consistency, I made the same mistake as the reviewers in the link you provided. I wrongly assumed that the main SQL would have made them all read consistent. Now that I think about it, that was a silly assumption since a trace would show independent (while recursive) SQL. It is a bit disturbing that the read inconsistency can happen with function calls within SQL. But it is good to know about in the event the "wrong results" appear for a query. That would be difficult to track down.

"bollocks"
:)
As I said before, I would try to get as much as possible in SQL, but when there are tuning issues with SQL that calls PLSQL functions, my answer can't always be "rewrite that function into a view". For years the mantra (not necessarily from you) has been "put everything in packages". Well, we have. And now developers want to build on that and use those functions within SQL. But then that brings me back to the original point of the question, that the CBO can not integrate what the PLSQL function calls are doing into the execution plan. The CBO can only look at the tables explicitly identified in the original SQL and then treat the function calls through what is effectively nested loop processing.

Perhaps I am over-analyzing the situation, but it is something I have been noticing more and more, and I think I will continue to see additional cases of it as we build upon our existing systems. Especially with the database-centric (thick database) approach that is becoming more common with the migration to web enabled applications (which formerly were just Forms apps).

I will look more into the scalar subquery caching. From what I read it is even better in 10gR2.

Thanks.
Andrew

Please clarify

Brian B, September 07, 2007 - 8:57 am UTC

You said above:
"1) if you can do it in all SQL - do it, never write code unless you are forced to.
2) if you absolutely cannot do it in sql, write as little plsql as you can (and understand how it works - scalar subquery caching for example can be of great benefit in your case sometimes)
3) if you cannot do it in plsql (because it requires doing something plsql is not capable of doing - which is very very little today actually) - write as little java as you can"

And you have said elsewhere: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:363719300346898052 )
"Use a transactional API in the form of stored procedures - do not allow applications to directly modify the tables - have them call these well formed, correct procedures"
and
"You *need* to centralize your transactional processing - using stored procedures - to *get the right stuff*. "

Is the difference between "avoid PL/SQL" in this thread and "use PL/SQL" in the other because this thread is focused on a READ and the other on a WRITE?
Tom Kyte
September 12, 2007 - 10:06 am UTC

there is no contradiction here.

A well formed transaction typically touches more than one object. A modification statement (insert, update, delete) touches one object. If you need to insert into t1, update t2, delete from t3 for your transaction - you need three statements.

Pl/sql is the way to bundle those statements into a single call into the database (more efficient, more clear, infinitely reusable)

What you would NOT do, if your transaction was "move data from T1 to T2" would be:

begin 
   for x in ( select * from T1 ) loop
       insert into t2 values X;
   end loop;
end;

you would:

begin
   insert into t2 select * from t1;
end;


do not do procedurally what you can do in a single sql statement.

No contradition, especially...

Duke Ganote, September 12, 2007 - 4:02 pm UTC

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library