You are on page 1of 13

Oracle PL/SQL Tuning and Optimization Techniques

Quiz Problems and Solutions

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 1

Tuning Data Structures - Before


File: slowds_q1.sql
ALTER TABLE employee ADD info VARCHAR2(2000); CREATE OR REPLACE PACKAGE comm_pkg IS TYPE reward_rt IS RECORD ( nm VARCHAR2(2000), sal NUMBER, comm NUMBER); TYPE reward_tt IS TABLE OF reward_rt INDEX BY BINARY_INTEGER; END; / CREATE OR REPLACE PROCEDURE fix_me ( nmfilter IN VARCHAR2, comm_list IN OUT comm_pkg.reward_tt ) IS v_nmfilter VARCHAR2(2000) NOT NULL := nmfilter; v_info VARCHAR2(2000); v_nth INTEGER; indx INTEGER; BEGIN FOR indx IN comm_list.FIRST .. comm_list.LAST LOOP v_nth := v_nth + 1; /* || Record date on which increase occurred; time is not || important. Job is run at noon and lasts 15 minutes. */ v_info := 'Doubled ' || v_nth || 'th employee''s salary on ' || SYSDATE || ' to ' || comm_list(indx).sal * 2; IF UPPER (comm_list(indx).nm) LIKE UPPER (v_nmfilter) THEN UPDATE employee SET salary := comm_list(indx).sal * 2, info := v_info, commission := comm_list(indx).comm WHERE last_name = UPPER (comm_list(indx).nm); comm_list(indx).comm := 0; END IF; END LOOP; END; /

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 2

Tuning Data Structures - After


File: slowds_a1.sql
/* 1. Move collection out of parameter list and into package spec */ CREATE OR REPLACE PACKAGE comm_pkg IS TYPE reward_rt IS RECORD ( nm VARCHAR2(2000), sal NUMBER, comm NUMBER); TYPE reward_tt IS TABLE OF reward_rt INDEX BY BINARY_INTEGER; rewards reward_tt; END; / CREATE OR REPLACE PROCEDURE fix_me ( nmfilter IN VARCHAR2 ) IS /* 2. Use %TYPE whenever appropriate to reduce memory requirements and improve robustness of code. */ /* 3. UPPER the name filter here and not in the loop. Remove NOT NULL constraint. */ v_nmfilter CONSTANT employee.last_name%TYPE := UPPER (nmfilter); v_name employee.last_name%TYPE; v_sal employee.salary%TYPE; /* 4. Use PLS_INTEGER */ /* 5. Grab first row in table to do optimal scanning code. */ indx PLS_INTEGER := comm_pkg.rewards.FIRST; /* 7. Date is not going to change, so capture and convert once. */ v_date CONSTANT VARCHAR2(10) := TO_CHAR (SYSDATE, 'MM/DD/YYYY'); /* 8. Use PLS_INTEGER */ v_first PLS_INTEGER; BEGIN /* 9. Do explicit NOT NULL check here */ IF nmfilter IS NOT NULL THEN v_first := indx - 1; LOOP EXIT WHEN indx IS NULL;

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 3

/* 10. Move repeated operations to local variables. */ v_name := UPPER (comm_pkg.rewards(indx).nm); v_sal := comm_pkg.rewards(indx).sal * 2; /* 11. Skip assignment to v_info; put in SQL directly. */ IF v_name LIKE v_nmfilter THEN UPDATE employee SET salary = v_sal, info = 'Doubled ' || TO_CHAR (indx - v_first) || 'th employee''s salary on ' || v_date || ' to ' || TO_CHAR (v_sal), commission = comm_pkg.rewards(indx).comm WHERE last_name = v_name; comm_pkg.rewards(indx).comm := 0; END IF; END LOOP; END IF; END; /

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 4

Present Value Calculation


Package Specification and Utilities
File: presvalue.sql
CREATE OR REPLACE PACKAGE pv IS /* Table structure to hold the lease accumulations. */ TYPE pv_table_type IS TABLE OF NUMBER(9) INDEX BY BINARY_INTEGER; pv_table pv_table_type; PROCEDURE showtab; PROCEDURE build_lease_schedule1(disp IN BOOLEAN := FALSE); PROCEDURE build_lease_schedule2(disp IN BOOLEAN := FALSE); PROCEDURE build_lease_schedule3(disp IN BOOLEAN := FALSE); END; / CREATE OR REPLACE PACKAGE BODY pv IS FUNCTION pv_of_fixed (year IN INTEGER) RETURN NUMBER IS BEGIN /* Dummy computation */ RETURN (year * 1.25); END; FUNCTION pv_of_variable (year IN INTEGER) RETURN NUMBER IS BEGIN /* Dummy computation */ RETURN (year / 1.25); END; PROCEDURE showtab IS indx INTEGER; BEGIN /* Display the results */ indx := pv_table.FIRST; LOOP EXIT WHEN indx IS NULL; DBMS_OUTPUT.PUT_LINE ( 'PV in year ' || indx || ' = ' || pv_table(indx)); indx := pv_table.NEXT (indx); END LOOP; END;

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 5

Present Value Calculation - Version 1


File: presvalue.sql
PROCEDURE build_lease_schedule1(disp IN BOOLEAN := FALSE) /* Construct present value lease schedule over 20 years. */ IS /* Temporary variable to hold lease accumulation. */ pv_total_lease NUMBER(9); BEGIN FOR year_count in 1 .. 20 LOOP /* Reset the lease amount for this year. */ pv_total_lease := 0; /* || Build the PV based on the remaining years || plus the fixed and variable amounts. */ FOR year_count2 in year_count..20 LOOP /* Add annual total lease amount to cummulative. */ pv_total_lease := pv_total_lease + pv_of_fixed (year_count2) + pv_of_variable (year_count2); END LOOP; /* Add the annual PV to the table. */ pv_table (year_count) := pv_total_lease; END LOOP; IF disp THEN showtab; END IF; END;

Hint for Tuning


Analyze the frequency of execution of the pv_of_* functions.

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 6

Present Value Calculation - Version 2


File: presvalue.sql
PROCEDURE build_lease_schedule2 (disp IN BOOLEAN := FALSE) IS one_year_pv NUMBER(9) := 0; pv_total_lease NUMBER(9) := 0; BEGIN /* || Build the 20-year accumulated total and save each || of the annual lease amounts to the PL/SQL table. Notice that || pv_table (N) is set to the annual lease amount for year N-1. */ FOR year_count in 1 .. 20 LOOP one_year_pv := pv_of_fixed (year_count) + pv_of_variable (year_count); pv_total_lease := pv_total_lease + one_year_pv; IF year_count < 20 THEN pv_table (year_count+1) := one_year_pv; END IF; END LOOP; /* Save the 20-year total in the first row. */ pv_table (1) := pv_total_lease; /* For each of the remaining years... */ FOR year_count IN 2 .. 20 LOOP /* Subtract the annual amount from the remaining total. */ pv_total_lease := pv_total_lease - pv_table (year_count); /* || Save the Nth accumulation to the table (this writes right || over the annual lease amount, which is no longer needed. || I get double use out of the pv_table in this way. */ pv_table (year_count) := pv_total_lease; END LOOP; IF disp THEN showtab; END IF; END;

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 7

Present Value Calculation - Version 3


File: presvalue.sql
PROCEDURE build_lease_schedule3 (disp IN BOOLEAN := FALSE) IS pv_total_lease NUMBER(9) := 0; one_year_pv NUMBER(9) := 0; BEGIN FOR year_count IN REVERSE 1..20 LOOP one_year_pv := pv_of_fixed(year_count) + pv_of_variable(year_count); pv_total_lease := pv_total_lease + one_year_pv; pv_table (year_count) := pv_total_lease; END LOOP; IF disp THEN showtab; END IF; END;

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 8

LStrip Function - Version 1


File: lstrip1.sf
CREATE OR REPLACE FUNCTION lstrip1 ( string_in IN VARCHAR2, substring_in IN VARCHAR2, num_in IN INTEGER := 1) RETURN VARCHAR2 IS BEGIN IF num_in < 1 OR string_in IS NULL OR substring_in IS NULL THEN RETURN string_in; ELSIF INSTR (string_in, substring_in) = 1 THEN RETURN lstrip1 ( SUBSTR (string_in, LENGTH (substring_in) + 1), substring_in, num_in - 1 ); ELSE RETURN string_in; END IF; END; /

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 9

LStrip Function - Version 2


File: lstrip2.sf
CREATE OR REPLACE FUNCTION lstrip2 ( string_in IN VARCHAR2, substring_in IN VARCHAR2, num_in IN INTEGER := 1) RETURN VARCHAR2 IS retval VARCHAR2(32767); subnum INTEGER := 0; sublen INTEGER := LENGTH (substring_in); subpos INTEGER; BEGIN retval := string_in; WHILE (subnum < num_in) LOOP subpos := INSTR (retval, substring_in, 1); IF (subpos = 1) THEN retval := SUBSTR (retval, sublen + 1); subnum := subnum + 1; ELSE subnum := num_in; END IF; END LOOP; RETURN retval; END lstrip2; /

Commentary
The algorithm in this case uses a WHILE loop. For each iteration of the loop body, it checks to see if the sub-string occurs at the beginning of the string. If so, that sub-string is stripped off using SUBSTR and tested again. So which is the better implementation (version 1 or version2)? Neither is particularly long or complex. One should, however, always be concerned about the use of recursion, because it can be very resource-intensive. - Danny Wagenaar (danny@duvel.fmv.ulg.ac.be):

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 10

LStrip Function - Version 3


File: lstrip3.sf
CREATE OR REPLACE FUNCTION lstrip3 ( string_in IN VARCHAR2, substring_in IN VARCHAR2, num_in IN INTEGER := 1) RETURN VARCHAR2 IS v_length NUMBER; v_clip VARCHAR2(32767); v_clipcount INTEGER := 0; BEGIN v_length := LENGTH (substring_in); WHILE v_clipcount < num_in LOOP v_clip := SUBSTR ( string_in, v_clipcount * v_length + 1, v_length); EXIT WHEN v_clip != substring_in; v_clipcount := v_clipcount + 1; END LOOP; RETURN SUBSTR (string_in, v_clipcount * v_length + 1); END; /

Commentary
"We avoided the use of the char_in parameter in our implementation. We were careful not to use recursion to make sure there would be no problem with nesting depth. Also, we do not use INSTR to see if the substring_in is at the beginning of string_in, since this could potentially lead to a search through 32767 bytes for a nonexistent pattern. "My new solution to the lstrip problem is significantly faster than the one I sent yesterday when used to strip large numbers of substrings from the input string. It is also much faster on stripping small iterations, although that is not really noticeable to the user unless the function is called a lot. On top of that, the code is smaller while maintaining readability." - Ken Geis and Jay Weiland (kgeis@cchem.berkeley.edu and jweiland@cchem.berkeley.edu)

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 11

DBMS_SQL Binding Optimization


Large Volume of Binding
File: slowsql_q2.sql
CREATE OR REPLACE PROCEDURE bindall IS cur INTEGER := DBMS_SQL.open_cursor; rows_inserted INTEGER; BEGIN DBMS_SQL.parse ( cur, 'INSERT INTO emp (empno, deptno, ename) VALUES (:empno, :deptno, :ename)', DBMS_SQL.native ); FOR rowind IN 1 .. 1000 LOOP DBMS_SQL.bind_variable (cur, 'empno', rowind); DBMS_SQL.bind_variable (cur, 'deptno', 40); DBMS_SQL.bind_variable (cur, 'ename', 'Steven' || rowind); rows_inserted := DBMS_SQL.execute (cur); END LOOP; DBMS_SQL.close_cursor (cur); END; /

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 12

Maybe We Can Avoid Binding Altogether!


File: slowsql_a2.sql
CREATE OR REPLACE PROCEDURE bindnone IS cur INTEGER := DBMS_SQL.open_cursor; rows_inserted INTEGER; BEGIN DBMS_SQL.parse ( cur, 'BEGIN INSERT INTO emp (empno, deptno, ename) VALUES (myvars.empno, myvars.deptno, myvars.ename); END;', DBMS_SQL.native ); FOR rowind IN 1 .. 1000 LOOP myvars.empno := rowind; myvars.deptno := 40; myvars.ename := 'Steven' || rowind; rows_inserted := DBMS_SQL.execute (cur); END LOOP; DBMS_SQL.close_cursor (cur); END; /

Run the slowsql_a2.tst to compare the performance.

Copyright 1999 Steven Feuerstein, PL/Solutions

Page 13

You might also like