Effective PL/SQL Thomas Kyte http://asktom.oracle.com/
Agenda <ul><li>Why PL/SQL? </li></ul><ul><li>Write as little as you can </li></ul><ul><li>Use Packages </li></ul><ul><li>U...
Why  PL/SQL
Why Use PL/SQL <ul><li>It is a ‘real’ language </li></ul><ul><ul><li>It is not a scripting language </li></ul></ul><ul><ul...
It is the most efficient language for data manipulation <ul><li>If your  goal  is to procedurally process data (after ensu...
It is the most efficient language for data manipulation <ul><li>SQL datatypes are PL/SQL datatypes </li></ul><ul><li>Tight...
It is the most efficient language for data manipulation static PreparedStatement  pstmt = null; public static void  proces...
PL/SQL epitomizes portability and reusability <ul><li>It is the most advanced portable language I’ve ever seen </li></ul><...
Many mistakes made in other languages using the database are avoided <ul><li>Bind Variables </li></ul><ul><ul><li>If you u...
However <ul><li>As with  any  language you can </li></ul><ul><ul><li>Write really good code </li></ul></ul><ul><ul><li>Wri...
Write as  Little as you can
Code… <ul><li>Write as much code: </li></ul><ul><ul><li>As you have to </li></ul></ul><ul><ul><li>But as little as you can...
Use PL/SQL constructs only when SQL cannot do it <ul><li>Another coding  ‘technique’   I see frequently: </li></ul><ul><li...
More Code = More Bugs Less Code = Less Bugs <ul><li>This  code  speaks for itself. </li></ul><ul><li>So does  this . </li>...
insert into t ( .... ) select EMPNO, STATUS_DATE, ....  from t1, t2, t3, t4, .... where ....; loop delete from t where (EM...
insert /* APPEND */ into t ( .... ) select EMPNO, STATUS_DATE, ...... from ( select EMPNO, STATUS_DATE, .... ,  max(STATUS...
/*  Load table t using history tables.  History tables have multiple records per employee.  We need to keep the history re...
/*  Load table t using history tables.  History tables have multiple records per employee.  We need to keep the history re...
<ul><li>“ Here is a last bit of advice on writing as little as possible: When you are writing code, make sure your routine...
Use  Packages
They break the dependency chain <ul><li>Most relevant in Oracle Database 10g Release 2 and before:  </li></ul>ops$tkyte%OR...
They increase your namespace <ul><li>You can have only one procedure P in a schema </li></ul><ul><li>With packages, you ca...
They support overloading <ul><li>A feature which is viewed as  </li></ul><ul><ul><li>Positive by some </li></ul></ul><ul><...
They support encapsulation <ul><li>Helps live up to the “fit on a screen” rule </li></ul><ul><ul><li>Many small subroutine...
Use  Static SQL
Static SQL is checked at compile time <ul><li>You know the SQL will (probably) execute </li></ul><ul><ul><li>It is syntact...
PL/SQL understands the dictionary <ul><li>It will create record types for you </li></ul><ul><li>It will allow you to defin...
One word - dependencies <ul><li>All referenced objects – tables, views, other bits of code, etc – are right there in the d...
Static SQL makes parse once, execute many a reality <ul><li>Dynamic SQL makes it easy to lose out on this benefit. </li></...
Dynamic SQL – when to use then? <ul><li>Dynamic SQL is something you want to use when static SQL is no longer practical—wh...
Bulk  Up
Bulk Processing Defined: <ul><li>A method to bother the database less often </li></ul><ul><li>A method to reduce round tri...
Bulk Processing <ul><li>You need to do it when… </li></ul><ul><ul><li>You retrieve data from the database </li></ul></ul><...
Bulk Processing For x in ( select * from t where … ) Loop process(x); update t set … where …; End loop; For x in (select *...
Bulk Processing create or replace procedure bulk as type ridArray is table of rowid; type onameArray is table  of t.object...
Bulk Processing SELECT ROWID RID, OBJECT_NAME FROM T T_BULK call  count  cpu  elapsed  disk  query  current  rows ------- ...
But of course, the bulkier the better… SELECT ROWID RID, OBJECT_NAME FROM T T_BULK call  count  cpu  elapsed  disk  query ...
Returning  Data
To return data to a client program  <ul><li>Either </li></ul><ul><ul><li>Simple, formal OUT parameters </li></ul></ul><ul>...
Using  Invokers Rights
Invokers versus Definers Rights <ul><li>Definers Rights </li></ul><ul><ul><li>Default method for stored compiled code </li...
Invokers Rights ops$tkyte%ORA11GR2> create table t 2  as 3  select 'hello world' text 4  from dual 5  / Table created. ops...
ops$tkyte%ORA11GR2> connect scott/tiger Connected. scott%ORA11GR2> exec ops$tkyte.ir hello world PL/SQL procedure successf...
scott%ORA11GR2> connect / Connected. ops$tkyte%ORA11GR2> create or replace procedure dr 2  AUTHID CURRENT_USER 3  as 4  be...
So, which on should we use? <ul><li>Definers Rights comes first, it is the default, it should be the default.  It is corre...
So, which on should we use? <ul><li>Invokers Rights can </li></ul><ul><ul><li>Lose the dependency mechanism  </li></ul></u...
Implicit  versus Explicit
Implicit versus Explicit <ul><li>Implicit  </li></ul><ul><ul><li>With this type of cursor, PL/SQL does most of the work fo...
Implicit versus Explicit <ul><li>There is a myth that explicit cursors are superior in performance and usability to implic...
Single Row Processing <ul><li>Implicit  </li></ul><ul><li>Explicit </li></ul><ul><li>These two bits of code do the same th...
Single Row Processing <ul><li>This is a bug </li></ul><ul><li>This is a bug I see a lot </li></ul><ul><li>It is bad, but a...
Multi-Row Processing <ul><li>Which is “easier”? </li></ul><ul><li>Which is more “bug free”? </li></ul><ul><li>Which one ar...
Multi-Row Processing <ul><li>This is nice too – all of the benefits of implicit, the factoring out of the SQL usually attr...
Multi-Row Processing <ul><li>So, is there ever a time to use explicit cursors? </li></ul><ul><ul><li>Never say Never </li>...
Beware  of…
Beware – When others <ul><li>When others </li></ul><ul><li>Autonomous Transactions </li></ul><ul><li>Triggers </li></ul>
When others then null; <ul><li>End users would never want to know there was a problem </li></ul><ul><li>Even if the “end u...
When others then null; ops$tkyte%ORA11GR2> create table t( x varchar2(4000) ); Table created. ops$tkyte%ORA11GR2> create o...
When others then null; ops$tkyte%ORA11GR2> alter procedure maintain_t compile 2  PLSQL_Warnings = 'enable:all' 3  reuse se...
When others then null; ops$tkyte%ORA11GR2> alter procedure maintain_t compile 2  PLSQL_Warnings = 'enable:all' 3  reuse se...
When others then null; ops$tkyte%ORA11GR2> create table t( x varchar2(4000) ); Table created. ops$tkyte%ORA11GR2> create o...
Beware – Autonomous Transactions <ul><li>Error logging routines – perfect </li></ul><ul><li>Virtually everything else –  b...
Beware – Triggers <ul><li>http://www.oracle.com/technology/oramag/oracle/08-sep/o58asktom.html </li></ul><ul><li>Maintenan...
Things  to do…
<ul><li>“ Instrument your code.  Make it debuggable.  Make it so that tracing the code is easy.  Identify yourself with DB...
<ul><li>“ Use the tools – SQL Developer, built in source code debugging.  Hierarchical profiling – for performance.  Traci...
<ul><li>“ Always test things out – especially advice.  I used to advise to use BULK COLLECT for everything.  That changed ...
<ul><li>“ Question Authority, Ask Questions” </li></ul>
 
Upcoming SlideShare
Loading in...5
×

Eff Plsql

2,949

Published on

Published in: Technology, Business
1 Comment
5 Likes
Statistics
Notes
  • http://dbmanagement.info/Tutorials/PLSQL.htm
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
2,949
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
251
Comments
1
Likes
5
Embeds 0
No embeds

No notes for slide

Eff Plsql

  1. 1. Effective PL/SQL Thomas Kyte http://asktom.oracle.com/
  2. 2. Agenda <ul><li>Why PL/SQL? </li></ul><ul><li>Write as little as you can </li></ul><ul><li>Use Packages </li></ul><ul><li>Use Static SQL </li></ul><ul><li>Bulk processing </li></ul><ul><li>Returning data </li></ul><ul><li>Invokers Rights </li></ul><ul><li>Implicit or Explicit? </li></ul><ul><li>Beware of some features </li></ul><ul><li>Things to definitely do </li></ul>
  3. 3. Why PL/SQL
  4. 4. Why Use PL/SQL <ul><li>It is a ‘real’ language </li></ul><ul><ul><li>It is not a scripting language </li></ul></ul><ul><ul><li>It is not a ‘toy’, it is used to code ‘real’ things </li></ul></ul><ul><li>It is the API to the database </li></ul>
  5. 5. It is the most efficient language for data manipulation <ul><li>If your goal is to procedurally process data (after ensuring a single SQL statement cannot do your work!) then PL/SQL is simply the most productive language to do so </li></ul>
  6. 6. It is the most efficient language for data manipulation <ul><li>SQL datatypes are PL/SQL datatypes </li></ul><ul><li>Tight coupling between the two languages </li></ul><ul><li>Code short cuts (implicit cursors) </li></ul><ul><li>Protected from many database changes </li></ul>Create or replace procedure process_data( p_inputs in varchar2 ) As Begin For x in ( select * from emp where ename like p_inputs ) Loop Process( X ); End loop End;
  7. 7. It is the most efficient language for data manipulation static PreparedStatement pstmt = null; public static void process_data ( Connection conn, String inputs ) throws Exception { int empno; String ename; String job; int mgr; String hiredate; int sal; int comm; int deptno; if ( pstmt == null ) pstmt = conn.prepareStatement (&quot;select * from emp &quot; + &quot;where ename like ? &quot; ); pstmt.setString( 1, inputs ); ResultSet rset = pstmt.executeQuery(); …… while( rset.next() ) { empno = rset.getInt(1); ename = rset.getString(2); job = rset.getString(3); mgr = rset.getInt(4); hiredate = rset.getString(5); sal = rset.getInt(6); comm = rset.getInt(7); deptno = rset.getInt(8); process( empno, ename, job, mgr, hiredate, sal, comm, deptno ); } rset.close(); } <ul><li>SQL datatypes are not Java types (consider number(38) issues…) </li></ul><ul><li>No coupling between the two languages, entirely procedural (what about SQLJ?) </li></ul><ul><li>No code short cuts (statement caching) </li></ul><ul><li>Not protected from many database changes (and no dependencies either!) </li></ul>
  8. 8. PL/SQL epitomizes portability and reusability <ul><li>It is the most advanced portable language I’ve ever seen </li></ul><ul><ul><li>It is callable from every other language out there </li></ul></ul><ul><ul><li>Anything that can connect to the database can use and reuse it </li></ul></ul><ul><li>Sure – there are things like SOA and Services that let X call Y </li></ul><ul><ul><li>But these introduce their own level complexities </li></ul></ul><ul><ul><li>And if your service is a database server, it would be best to be written in the database </li></ul></ul><ul><li>If you can connect to the database – you can use and reuse PL/SQL from anything </li></ul>
  9. 9. Many mistakes made in other languages using the database are avoided <ul><li>Bind Variables </li></ul><ul><ul><li>If you use static SQL in PL/SQL it is impossible to not use bind variables correctly. </li></ul></ul><ul><ul><li>You have to use cumbersome dynamic SQL to do it wrong. </li></ul></ul><ul><li>Parse Once, Execute many </li></ul><ul><ul><li>PL/SQL does statement caching </li></ul></ul><ul><ul><li>You have to either configure and enable its use in other languages or </li></ul></ul><ul><ul><li>Do it your self (refer back to java code) </li></ul></ul><ul><li>Schema Changes are safer </li></ul><ul><ul><li>Alter table t modify c1 varchar2(255); </li></ul></ul><ul><ul><li>We can find all uses of T ( I didn’t know you were using that, sorry..) </li></ul></ul><ul><ul><li>We can make the change without having to change code </li></ul></ul>
  10. 10. However <ul><li>As with any language you can </li></ul><ul><ul><li>Write really good code </li></ul></ul><ul><ul><li>Write really average code </li></ul></ul><ul><ul><li>Write really really really bad code </li></ul></ul><ul><li>You can make the same mistakes with PL/SQL that you can with every other language </li></ul><ul><ul><li>By not understanding the language </li></ul></ul><ul><ul><li>By not understanding some implementation details </li></ul></ul><ul><ul><li>By not understanding SQL </li></ul></ul><ul><ul><li>By not designing </li></ul></ul><ul><ul><li>And so on… </li></ul></ul>
  11. 11. Write as Little as you can
  12. 12. Code… <ul><li>Write as much code: </li></ul><ul><ul><li>As you have to </li></ul></ul><ul><ul><li>But as little as you can… </li></ul></ul><ul><li>Think in SETS </li></ul><ul><li>Use (really use – not just ‘use’) SQL </li></ul>Begin For x in ( select * from table@remote_db ) Loop Insert into table ( c1, c2, … ) values ( x.c1, x.c2,… ); End loop; End; Insert into table (c1,c2,…) select c1,c2,…. From table@remote_db Insert into table (c1,c2,…) select c1,c2,…. From table@remote_db LOG ERRORS ( some_variable ) REJECT LIMIT UNLIMITED; … code to handle errors for tag some_variable …
  13. 13. Use PL/SQL constructs only when SQL cannot do it <ul><li>Another coding ‘technique’ I see frequently: </li></ul><ul><li>The developer did not want to “burden” the database with a join </li></ul>For a in ( select * from t1 ) Loop For b in ( select * from t2 where t2.key = t1.key ) Loop For c in ( select * from t3 where t3.key = t2.key ) Loop …
  14. 14. More Code = More Bugs Less Code = Less Bugs <ul><li>This code speaks for itself. </li></ul><ul><li>So does this . </li></ul><ul><li>Always look at the procedural code and ask yourself “is there a set based way to do this algorithm ” </li></ul><ul><ul><li>For example … </li></ul></ul>
  15. 15. insert into t ( .... ) select EMPNO, STATUS_DATE, .... from t1, t2, t3, t4, .... where ....; loop delete from t where (EMPNO,STATUS_DATE) in ( select EMPNO, min(STATUS_DATE) from t group by EMPNO having count(1) > 1 ); exit when sql%rowcount = 0; end loop; For any set of records with more than one EMPNO, remove rows with the oldest STATUS_DATE. Additionally – If the last set of EMPNO records all have the same STATUS_DATE, remove them all. More Code = More Bugs Less Code = Less Bugs EMPNO STATUS_DATE ------- ------------ 1 01-jan-2009 1 15-jun-2009 1 01-sep-2009 … 1000 01-feb-2009 1000 22-aug-2009 1000 10-oct-2009 1000 10-oct-2009 EMPNO STATUS_DATE ------- ------------ 1 01-sep-2009 … (1,01-jan-2009,3) (1001,01-feb-2009,4) EMPNO STATUS_DATE ------- ------------ 1 01-jan-2009 1 15-jun-2009 1 01-sep-2009 … 1000 01-feb-2009 1000 22-aug-2009 1000 10-oct-2009 1000 10-oct-2009 EMPNO STATUS_DATE ------- ------------ 1 01-jan-2009 1 15-jun-2009 1 01-sep-2009 … 1000 01-feb-2009 1000 22-aug-2009 1000 10-oct-2009 1000 10-oct-2009 (1,15-jun-2009,2) (1001,22-aug-2009,3) EMPNO STATUS_DATE ------- ------------ 1 01-jan-2009 1 15-jun-2009 1 01-sep-2009 … 1000 01-feb-2009 1000 22-aug-2009 1000 10-oct-2009 1000 10-oct-2009 (1001,22-aug-2009,2)
  16. 16. insert /* APPEND */ into t ( .... ) select EMPNO, STATUS_DATE, ...... from ( select EMPNO, STATUS_DATE, .... , max(STATUS_DATE) OVER ( partition by EMPNO ) max_sd, count(EMPNO) OVER ( partition by EMPNO,STATUS_DATE ) cnt from t1, t2, t3, t4, … where … ) where STATUS_DATE = max_sd and cnt = 1; More Code = More Bugs Less Code = Less Bugs <ul><li>This was a data warehouse load (load 2-3-4 times the data you want, then delete? Ouch) </li></ul><ul><li>It was wrong – procedural code is no easier to understand than set based code, documentation is key </li></ul>
  17. 17. /* Load table t using history tables. History tables have multiple records per employee. We need to keep the history records for each employee that have the maximum status date for that employee. We do that by computing the max(status_date) for each employee (partition by EMPNO finding max(status_date) and keeping only the records such that the status_date for that record = max(status_date) for all records with same empno */ insert /* APPEND */ into t ( .... ) select EMPNO, STATUS_DATE, ...... from ( select EMPNO, STATUS_DATE, .... , max(STATUS_DATE) OVER ( partition by EMPNO ) max_sd from t1, t2, t3, t4, … where … ) where STATUS_DATE = max_sd; More Code = More Bugs Less Code = Less Bugs
  18. 18. /* Load table t using history tables. History tables have multiple records per employee. We need to keep the history records for each employee that have the maximum status date for that employee. We do that by numbering each history record by empno, keeping only the records such that it is the first record for a EMPNO after sorting by status_date desc. REALIZE: this is not deterministic if there are two status_dates that are the same for a given employee! */ insert /* APPEND */ into t ( .... ) select EMPNO, STATUS_DATE, ...... from ( select EMPNO, STATUS_DATE, .... , row_number() OVER ( partition by EMPNO order by STATUS_DATE desc ) rn from t1, t2, t3, t4, … where … ) where rn=1; More Code = More Bugs Less Code = Less Bugs
  19. 19. <ul><li>“ Here is a last bit of advice on writing as little as possible: When you are writing code, make sure your routines (methods, procedures, functions, or whatever you want to call them) fit on a screen. You should be able to see the logic from start to finish on your monitor. Buy the biggest monitor you can, and make the routines fit on your screen. This rule forces you to think modularly, so you break up the code into bite-sized snippets that are more easily understood” </li></ul>
  20. 20. Use Packages
  21. 21. They break the dependency chain <ul><li>Most relevant in Oracle Database 10g Release 2 and before: </li></ul>ops$tkyte%ORA10GR2> create or replace procedure p1 as begin null; end; ops$tkyte%ORA10GR2> create or replace procedure p2 as begin p1; end; OBJECT_NAME STATUS TO_CHAR(LAST_DD ------------------------------ ------- --------------- P1 VALID 04-oct 12:15:54 P2 VALID 04-oct 12:15:54 ops$tkyte%ORA10GR2> create or replace procedure p1 as begin /* updated */ null; end; OBJECT_NAME STATUS TO_CHAR(LAST_DD ------------------------------ ------- --------------- P1 VALID 04-oct 12:15:58 P2 INVALID 04-oct 12:15:54
  22. 22. They increase your namespace <ul><li>You can have only one procedure P in a schema </li></ul><ul><li>With packages, you can have as many procedure P’s as you need </li></ul><ul><ul><li>Less chance of developer X using the same ‘name’ as developer Y since only package names would clash </li></ul></ul><ul><li>A single package has many procedures/functions </li></ul><ul><ul><li>Reduces dictionary “clutter” </li></ul></ul><ul><ul><li>Organizes things, related code goes together </li></ul></ul><ul><ul><li>Promotes modularity </li></ul></ul>
  23. 23. They support overloading <ul><li>A feature which is viewed as </li></ul><ul><ul><li>Positive by some </li></ul></ul><ul><ul><li>Negative by others </li></ul></ul><ul><li>Overloading can be very useful in API packages </li></ul><ul><ul><li>259 out of 728 ‘SYS’ packages employ this technique </li></ul></ul>
  24. 24. They support encapsulation <ul><li>Helps live up to the “fit on a screen” rule </li></ul><ul><ul><li>Many small subroutines that are no use outside of the package </li></ul></ul><ul><ul><li>Hide them in the package body, no one can see them </li></ul></ul><ul><ul><li>Reduces clutter in the dictionary </li></ul></ul><ul><li>Allows you to group related functionality together </li></ul><ul><ul><li>Makes it obvious what pieces of code are to be used together </li></ul></ul><ul><li>They support elaboration code </li></ul><ul><ul><li>When package is first invoked, complex initialization code may be executed </li></ul></ul>
  25. 25. Use Static SQL
  26. 26. Static SQL is checked at compile time <ul><li>You know the SQL will (probably) execute </li></ul><ul><ul><li>It is syntactically correct </li></ul></ul><ul><ul><li>It could still raise an error (divide by zero, conversion error, etc) </li></ul></ul><ul><ul><li>It might be semantically incorrect, but that is a bug in your logic, not a criticism of static SQL </li></ul></ul>
  27. 27. PL/SQL understands the dictionary <ul><li>It will create record types for you </li></ul><ul><li>It will allow you to define variables based on the database types </li></ul><ul><li>The compiler does more work, so you don’t have to. </li></ul>
  28. 28. One word - dependencies <ul><li>All referenced objects – tables, views, other bits of code, etc – are right there in the dictionary. </li></ul><ul><li>No more “Oh sorry, I didn’t know you were using that” </li></ul><ul><li>If something changes – we know right away if something is broken </li></ul><ul><ul><li>Grants – lose one that you need, code will stay invalid </li></ul></ul><ul><ul><li>Drop column – drop one that you reference, code will stay invalid </li></ul></ul><ul><ul><li>Modify length of column – if you reference that, code will recompile with new size. </li></ul></ul>
  29. 29. Static SQL makes parse once, execute many a reality <ul><li>Dynamic SQL makes it easy to lose out on this benefit. </li></ul><ul><li>With DBMS_SQL, you have to cache the ‘cursor’ yourself and make sure you use it over and over (eg: do not call dbms_sql.close() until you have to ) </li></ul><ul><li>With native dynamic SQL, you need to make sure you use the same SQL text over and over to cache statements </li></ul><ul><ul><li>And if you are doing that, why did you use dynamic SQL again? </li></ul></ul><ul><ul><li>Different in 9i and before than 10g and later </li></ul></ul><ul><li>Impossible to be SQL Injected with static SQL! Far too easy to be SQL Injected with dynamic SQL. </li></ul>
  30. 30. Dynamic SQL – when to use then? <ul><li>Dynamic SQL is something you want to use when static SQL is no longer practical—when you would be writing hundreds or thousands of lines of code, and can replace it with a very small bit of safe (sql injection) dynamic SQL. </li></ul><ul><li>When you’ve shown that using static SQL would not be practice – that is, it is never your first choice . </li></ul>
  31. 31. Bulk Up
  32. 32. Bulk Processing Defined: <ul><li>A method to bother the database less often </li></ul><ul><li>A method to reduce round trips (even from PL/SQL to SQL – there is a ‘round trip’ involved </li></ul><ul><li>A method to utilize fewer resources in general </li></ul><ul><li>A method to maintain data structures in better shape </li></ul><ul><li>You get some data (more than a row), process it, and send it all back (to update/insert/delete). </li></ul>
  33. 33. Bulk Processing <ul><li>You need to do it when… </li></ul><ul><ul><li>You retrieve data from the database </li></ul></ul><ul><ul><li>AND you send it back to the database </li></ul></ul><ul><li>You need NOT do it when… </li></ul><ul><ul><li>You retrieve data from the database </li></ul></ul><ul><ul><li><this space left intentionally blank> </li></ul></ul><ul><ul><li>For example… </li></ul></ul>
  34. 34. Bulk Processing For x in ( select * from t where … ) Loop process(x); update t set … where …; End loop; For x in (select * from t where …) Loop dbms_output.put_line( … t.x … ); End loop; <ul><li>You need to do it when… </li></ul><ul><ul><ul><li>Implicit array fetch for select </li></ul></ul></ul><ul><ul><ul><li>Not so for update… Details on next slide </li></ul></ul></ul><ul><li>You need NOT do it when… </li></ul><ul><ul><ul><li>Implicit array fetch for select </li></ul></ul></ul><ul><ul><ul><li>No going back to database </li></ul></ul></ul>
  35. 35. Bulk Processing create or replace procedure bulk as type ridArray is table of rowid; type onameArray is table of t.object_name%type; cursor c is select rowid rid, object_name from t t_bulk; l_rids ridArray; l_onames onameArray; N number := 100; begin open c; loop fetch c bulk collect into l_rids, l_onames limit N; for i in 1 .. l_rids.count loop l_onames(i) := substr(l_onames(i),2) ||substr(l_onames(i),1,1); end loop; forall i in 1 .. l_rids.count update t set object_name = l_onames(i) where rowid = l_rids(i); exit when c%notfound; end loop; close c; end; create or replace procedure slow_by_slow as begin for x in (select rowid rid, object_name from t t_slow_by_slow) loop x.object_name := substr(x.object_name,2) ||substr(x.object_name,1,1); update t set object_name = x.object_name where rowid = x.rid; end loop; end;
  36. 36. Bulk Processing SELECT ROWID RID, OBJECT_NAME FROM T T_BULK call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 721 0.17 0.17 0 22582 0 71825 ******************************************************************************** UPDATE T SET OBJECT_NAME = :B1 WHERE ROWID = :B2 call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 719 12.83 13.77 0 71853 74185 71825 Fetch 0 0.00 0.00 0 0 0 0 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 720 12.83 13.77 0 71853 74185 71825 SELECT ROWID RID, OBJECT_NAME FROM T T_SLOW_BY_SLOW call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 721 0.17 0.17 0 22582 0 71825 ******************************************************************************** UPDATE T SET OBJECT_NAME = :B2 WHERE ROWID = :B1 call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 71824 21.25 22.25 0 71836 73950 71824 Fetch 0 0.00 0.00 0 0 0 0 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 71825 21.25 22.25 0 71836 73950 71824
  37. 37. But of course, the bulkier the better… SELECT ROWID RID, OBJECT_NAME FROM T T_BULK call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 721 0.17 0.17 0 22582 0 71825 ******************************************************************************** UPDATE T SET OBJECT_NAME = :B1 WHERE ROWID = :B2 call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 719 12.83 13.77 0 71853 74185 71825 Fetch 0 0.00 0.00 0 0 0 0 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 720 12.83 13.77 0 71853 74185 71825 update t set object_name = substr(object_name,2) || substr(object_name,1,1) call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 1.30 1.44 0 2166 75736 71825 Fetch 0 0.00 0.00 0 0 0 0 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 2 1.30 1.44 0 2166 75736 71825 Lots less code too! (dml error logging if you need)
  38. 38. Returning Data
  39. 39. To return data to a client program <ul><li>Either </li></ul><ul><ul><li>Simple, formal OUT parameters </li></ul></ul><ul><ul><li>Ref cursor for all result sets </li></ul></ul><ul><li>Do not run a query </li></ul><ul><ul><li>To populate a collection </li></ul></ul><ul><ul><li>To return collection to client </li></ul></ul><ul><li>Just run the query (open C for SQL) </li></ul><ul><ul><li>Ease of programming, everything can handle a cursor </li></ul></ul><ul><ul><li>Flexibility (client decides how many rows to deal with, less memory intensive) </li></ul></ul><ul><ul><li>Performance – client might never get to the last row (probably won’t) </li></ul></ul>
  40. 40. Using Invokers Rights
  41. 41. Invokers versus Definers Rights <ul><li>Definers Rights </li></ul><ul><ul><li>Default method for stored compiled code </li></ul></ul><ul><ul><li>All code is compiled with base (no roles) privileges granted directly to the owner of the procedure. </li></ul></ul><ul><li>Invokers Rights </li></ul><ul><ul><li>Default method for anonymous blocks </li></ul></ul><ul><ul><li>Using authid current_user – All code is compiled with base privileges granted directly to the owner of the procedure. </li></ul></ul><ul><ul><li>SQL rights are re-evaluated at runtime using the current set of privileges in place in the current session running the code. </li></ul></ul><ul><ul><li>Tables accessed at runtime might be different than tables checked against at compile time. </li></ul></ul>
  42. 42. Invokers Rights ops$tkyte%ORA11GR2> create table t 2 as 3 select 'hello world' text 4 from dual 5 / Table created. ops$tkyte%ORA11GR2> create or replace procedure IR 2 as 3 begin 4 for x in ( select * from t) 5 loop 6 dbms_output.put_line(x.text); 7 end loop; 8 end; 9 / Procedure created. ops$tkyte%ORA11GR2> grant all on ops$tkyte.ir to public; Grant succeeded. ops$tkyte%ORA11GR2> create or replace procedure DR 2 AUTHID CURRENT_USER 3 as 4 begin 5 for x in ( select * from t) 6 loop 7 dbms_output.put_line(x.text); 8 end loop; 9 end; 10 / Procedure created. ops$tkyte%ORA11GR2> grant all on ops$tkyte.dr to public; Grant succeeded.
  43. 43. ops$tkyte%ORA11GR2> connect scott/tiger Connected. scott%ORA11GR2> exec ops$tkyte.ir hello world PL/SQL procedure successfully completed. scott%ORA11GR2> exec ops$tkyte.dr BEGIN ops$tkyte.dr; END; * ERROR at line 1: ORA-00942: table or view does not exist ORA-06512: at &quot;OPS$TKYTE.DR&quot;, line 5 ORA-06512: at line 1 scott%ORA11GR2> connect / Connected. ops$tkyte%ORA11GR2> grant select on t to scott; Grant succeeded. ops$tkyte%ORA11GR2> connect scott/tiger Connected. scott%ORA11GR2> exec ops$tkyte.dr BEGIN ops$tkyte.dr; END; * ERROR at line 1: ORA-00942: table or view does not exist ORA-06512: at &quot;OPS$TKYTE.DR&quot;, line 5 ORA-06512: at line 1 Invokers Rights
  44. 44. scott%ORA11GR2> connect / Connected. ops$tkyte%ORA11GR2> create or replace procedure dr 2 AUTHID CURRENT_USER 3 as 4 begin 5 for x in ( select * from OPS$TKYTE.t) 6 loop 7 dbms_output.put_line(x.text); 8 end loop; 9 end; 10 / Procedure created. ops$tkyte%ORA11GR2> connect scott/tiger Connected. scott%ORA11GR2> exec ops$tkyte.dr hello world PL/SQL procedure successfully completed. scott%ORA11GR2> connect / Connected. ops$tkyte%ORA11GR2> revoke select on t from scott; Revoke succeeded. ops$tkyte%ORA11GR2> connect scott/tiger Connected. scott%ORA11GR2> exec ops$tkyte.dr BEGIN ops$tkyte.dr; END; * ERROR at line 1: ORA-00942: table or view does not exist ORA-06512: at &quot;OPS$TKYTE.DR&quot;, line 5 ORA-06512: at line 1 Invokers Rights
  45. 45. So, which on should we use? <ul><li>Definers Rights comes first, it is the default, it should be the default. It is correct 99.99999999% of the time </li></ul><ul><li>Invokers Rights are good for </li></ul><ul><ul><li>Routines that need to run “as caller”. Running as the owner would/could cause security issues (think SYS owned code) </li></ul></ul><ul><ul><li>It is infrequently executed or doesn’t use SQL </li></ul></ul><ul><ul><li>It uses dynamic SQL (typically). It is not bound to a fixed set of tables. It is a utility </li></ul></ul><ul><ul><ul><li>DUMP_CSV() </li></ul></ul></ul><ul><ul><ul><li>PRINT_TABLE() </li></ul></ul></ul>
  46. 46. So, which on should we use? <ul><li>Invokers Rights can </li></ul><ul><ul><li>Lose the dependency mechanism </li></ul></ul><ul><ul><ul><li>They typically use dynamic SQL </li></ul></ul></ul><ul><ul><li>Reduce the reliability of the stored code </li></ul></ul><ul><ul><ul><li>No compile time security checks </li></ul></ul></ul><ul><ul><ul><li>Datatypes are not known until runtime </li></ul></ul></ul><ul><ul><ul><li>Everything good about static SQL is lost typically </li></ul></ul></ul><ul><ul><li>Since SQL object references are not known until runtime and could be different for everyone, you could flood the shared pool </li></ul></ul><ul><ul><li>It is harder to develop and debug </li></ul></ul><ul><ul><li>You lose the advantages of static SQL in PL/SQL </li></ul></ul>
  47. 47. Implicit versus Explicit
  48. 48. Implicit versus Explicit <ul><li>Implicit </li></ul><ul><ul><li>With this type of cursor, PL/SQL does most of the work for you. You don’t have to open close, declare, or fetch from an implicit cursor. </li></ul></ul><ul><li>Explicit </li></ul><ul><ul><li>With this type of cursor, you do all of the work. You must open, close, fetch, and control an explicit cursor completely. </li></ul></ul>For x in ( select * from t where … ) Loop … End loop; Declare cursor c is select * from t where …; l_rec c%rowtype; Open c; Loop fetch c into l_rec; exit when c%notfound; … End loop; Close c;
  49. 49. Implicit versus Explicit <ul><li>There is a myth that explicit cursors are superior in performance and usability to implicit cursors. </li></ul><ul><li>The opposite is generally true </li></ul><ul><ul><li>Implicit cursors have implicit array fetching, Explicit cursors do not </li></ul></ul><ul><ul><li>Implicit cursors have many of their operations hidden in the PL/SQL runtime (C code) as opposed to explicit cursors being coded by you in PL/SQL code. </li></ul></ul><ul><ul><li>Implicit cursors are safer than explicit cursors code-wise </li></ul></ul><ul><ul><ul><li>Select into checks (at least and at most one row) </li></ul></ul></ul><ul><ul><ul><li>Cursors opened/closed for you – implicitly – no ‘leaks’ </li></ul></ul></ul><ul><ul><ul><li>Both implicit and explicit cursors however are cached by PL/SQL </li></ul></ul></ul><ul><ul><ul><ul><li>But ref cursors are not… </li></ul></ul></ul></ul>
  50. 50. Single Row Processing <ul><li>Implicit </li></ul><ul><li>Explicit </li></ul><ul><li>These two bits of code do the same thing. Which is more efficient? </li></ul>Select … INTO <plsql variables> from … where …; Declare cursor c is select … from … where …; Begin open c; fetch c into <plsql variables>; if (c%notfound) then raise no_data_found; end if; fetch c into <plsql variables>; if (c%found) then raise too_many_rows; end if; close c;
  51. 51. Single Row Processing <ul><li>This is a bug </li></ul><ul><li>This is a bug I see a lot </li></ul><ul><li>It is bad, but at least it probably returns NULL </li></ul><ul><li>This is a ‘worse’ bug </li></ul><ul><li>This I see even more </li></ul><ul><li>Combines the “do it yourself join” with “do it the hard way” to get the wrong answer after a longer period of time with lots more development time </li></ul>Function get_something return … is cursor c is select … from … where …; Begin open c; fetch c into <plsql variables>; close c; return plsql variables ---------------------------------------- Begin open c1; loop fetch c1 into l_data; exit when c1%notfound; open c2( l_data ); fetch c2 into l_something_else; close c2; process( l_data, l_something_else); end loop; close c1;
  52. 52. Multi-Row Processing <ul><li>Which is “easier”? </li></ul><ul><li>Which is more “bug free”? </li></ul><ul><li>Which one array fetches for us? </li></ul><ul><li>The explicit code would be a lot more intense if we wanted to array fetch. </li></ul>create or replace procedure implicit as begin for x in ( select * from dept ) loop null; end loop; end; ---------------------------------------- create or replace procedure explicit as l_rec dept%rowtype; cursor c is select * from dept; begin open c; loop fetch c into l_rec; exit when c%notfound; end loop; close c; end;
  53. 53. Multi-Row Processing <ul><li>This is nice too – all of the benefits of implicit, the factoring out of the SQL usually attributed to explicit cursors. </li></ul><ul><li>This code in the tiny font (to make it fit on the screen) does the same thing as the above bit of code. Which is more efficient? Which is less bug likely? </li></ul>create or replace procedure implicit As cursor c is select * from dept; begin for x in C loop null; end loop; end; ---------------------------------------- create or replace procedure explicit as type array is table of dept%rowtype; l_rec array; n number := 2; cursor c is select * from dept; begin open c; loop fetch c bulk collect into l_rec limit N; for i in 1 .. l_rec.count loop null; end loop; exit when c%notfound; end loop; close c; end;
  54. 54. Multi-Row Processing <ul><li>So, is there ever a time to use explicit cursors? </li></ul><ul><ul><li>Never say Never </li></ul></ul><ul><ul><li>Never say Always </li></ul></ul><ul><ul><li>I always say </li></ul></ul><ul><li>Bulk forall processing probably mandates explicit cursors </li></ul><ul><li>Ref Cursors mandate explicit cursors </li></ul>create or replace procedure bulk as type ridArray is table of rowid; type onameArray is table of t.object_name%type; cursor c is select rowid rid, object_name from t t_bulk; l_rids ridArray; l_onames onameArray; N number := 100; begin open c; loop fetch c bulk collect into l_rids, l_onames limit N; for i in 1 .. l_rids.count loop l_onames(i) := substr(l_onames(i),2) ||substr(l_onames(i),1,1); end loop; forall i in 1 .. l_rids.count update t set object_name = l_onames(i) where rowid = l_rids(i); exit when c%notfound; end loop; close c; end;
  55. 55. Beware of…
  56. 56. Beware – When others <ul><li>When others </li></ul><ul><li>Autonomous Transactions </li></ul><ul><li>Triggers </li></ul>
  57. 57. When others then null; <ul><li>End users would never want to know there was a problem </li></ul><ul><li>Even if the “end user” is really another module calling you </li></ul><ul><li>Just log it – don’t raise it </li></ul>Begin … Exception When others Then log_error( …); End;
  58. 58. When others then null; ops$tkyte%ORA11GR2> create table t( x varchar2(4000) ); Table created. ops$tkyte%ORA11GR2> create or replace 2 procedure maintain_t 3 ( p_str in varchar2 ) 4 as 5 begin 6 insert into t (x) values ( 'hello' ); 7 insert into t (x) values ( p_str ); 8 insert into t (x) values ( 'world' ); 9 exception 10 when others 11 then 12 -- call some log_error() routine 13 null; 14 end; 15 / Procedure created. ops$tkyte%ORA11GR2> exec maintain_t( rpad( 'x', 4001, 'x' ) ); PL/SQL procedure successfully completed. ops$tkyte%ORA11GR2> select * from t; X -------------------- hello 1 row selected.
  59. 59. When others then null; ops$tkyte%ORA11GR2> alter procedure maintain_t compile 2 PLSQL_Warnings = 'enable:all' 3 reuse settings 4 / SP2-0805: Procedure altered with compilation warnings ops$tkyte%ORA11GR2> show errors procedure maintain_t Errors for PROCEDURE MAINTAIN_T: LINE/COL ERROR -------- ----------------------------------------------------------------- 1/1 PLW-05018: unit MAINTAIN_T omitted optional AUTHID clause; default value DEFINER used 9/8 PLW-06009: procedure &quot;MAINTAIN_T&quot; OTHERS handler does not end in RAISE or RAISE_APPLICATION_ERROR
  60. 60. When others then null; ops$tkyte%ORA11GR2> alter procedure maintain_t compile 2 PLSQL_Warnings = 'enable:all' 3 reuse settings 4 / SP2-0805: Procedure altered with compilation warnings ops$tkyte%ORA11GR2> show errors procedure maintain_t Errors for PROCEDURE MAINTAIN_T: LINE/COL ERROR -------- ----------------------------------------------------------------- 1/1 PLW-05018: unit MAINTAIN_T omitted optional AUTHID clause; default value DEFINER used 9/8 PLW-06009: You have a bug in your code. Repeat: You have a BUG IN YOUR CODE. Procedure &quot;MAINTAIN_T&quot; OTHERS handler does not end in RAISE or RAISE_APPLICATION_ERROR. Fix it right now. <ul><li>If they had asked me to write the warning message it would read like this: </li></ul>
  61. 61. When others then null; ops$tkyte%ORA11GR2> create table t( x varchar2(4000) ); Table created. ops$tkyte%ORA11GR2> create or replace 2 procedure maintain_t 3 ( p_str in varchar2 ) 4 as 5 begin 6 insert into t (x) values ( 'hello' ); 7 insert into t (x) values ( p_str ); 8 insert into t (x) values ( 'world' ); 9 exception 10 when others 11 then 12 -- call some log_error() routine 13 RAISE; 14 end; 15 / Procedure created. ops$tkyte%ORA11GR2> exec maintain_t( rpad( 'x', 4001, 'x' ) ); BEGIN maintain_t( rpad( 'x', 4001, 'x' ) ); END; * ERROR at line 1: ORA-01461: can bind a LONG value only for insert into a LONG column ORA-06512: at &quot;OPS$TKYTE.MAINTAIN_T&quot;, line 12 ORA-06512: at line 1 ops$tkyte%ORA11GR2> select * from t; no rows selected
  62. 62. Beware – Autonomous Transactions <ul><li>Error logging routines – perfect </li></ul><ul><li>Virtually everything else – bad idea </li></ul><ul><li>They are not good for mutating table issues </li></ul><ul><li>They are not good for multi-part transactions </li></ul><ul><li>They are not good for committing in triggers (nothing is) </li></ul><ul><li>They are good for implementing sequences (oh wait… we did that) </li></ul><ul><li>They are good for implementing recursive transactions to perform space management (oh wait…) </li></ul><ul><li>They are good for implementing recursive transactions to do auditing that cannot be rolled back (oh wait…) </li></ul><ul><li>They are good for implementing recursive transactions to dynamically add partitions to tables as data arrives (oh wait…) </li></ul>
  63. 63. Beware – Triggers <ul><li>http://www.oracle.com/technology/oramag/oracle/08-sep/o58asktom.html </li></ul><ul><li>Maintenance Headache </li></ul><ul><ul><li>Why is my data changing like that? </li></ul></ul><ul><ul><li>They are ‘magic’, a side effect, unexpected, forgotten about. </li></ul></ul><ul><li>Incorrect Implementations </li></ul><ul><ul><li>Most triggers that cross rows in tables (eg: uniqueness) or cross tables (eg: referential integrity) are coded wrong – they did not lock the table. </li></ul></ul>
  64. 64. Things to do…
  65. 65. <ul><li>“ Instrument your code. Make it debuggable. Make it so that tracing the code is easy. Identify yourself with DBMS_SESSION. Now DBMS_MONITOR can trace you. Add copious calls to DBMS_APPLICATION_INFO, now we can see who you are, what you are doing and how long you’ve been doing it. Add calls to a logging package (for example http://log4plsql.sourceforge.net/) to enable remote debugging of your code. Use conditional compilation to enable extra intensive debug code to be ‘deployed’ to production.” </li></ul>
  66. 66. <ul><li>“ Use the tools – SQL Developer, built in source code debugging. Hierarchical profiling – for performance. Tracing tools to see how your SQL is doing. ASH/AWR reports. PL/SCOPE. Learn the tools, then use them.” </li></ul>
  67. 67. <ul><li>“ Always test things out – especially advice. I used to advise to use BULK COLLECT for everything. That changed in Oracle Database 10g when they started implicitly doing that for us. There is advice on the ‘internet’ to never use implicit cursor – always use explicit. It is wrong. If they suggest it is “faster” and you cannot see it being “faster”, question the advice.” </li></ul>
  68. 68. <ul><li>“ Question Authority, Ask Questions” </li></ul>
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×