Download presentation
Presentation is loading. Please wait.
Published byMichael Hardy Modified over 8 years ago
1
Matt Owen mowen@cuesta.edu Cuesta College
2
Writing maintainable code Data structures Custom exceptions Design patterns and frameworks Unit Testing Test Driven Development Common performance issues
3
Maintainable Code Has Naming Conventions Has Descriptive Variables Has No Magic Numbers Has Abstraction Has Useful Comments Is easy to read and comprehend Bad Code Has No Consistency Has Poorly Named Variables Has Magic Numbers Has Redundant Code Has Useless Comments
4
DECLARE CURSOR cur IS SELECT spriden_first_name, spriden_last_name, spriden_mi FROM spriden, spbpers WHERE spriden_change_ind IS NULL AND spriden_pidm = spbpers_pidm AND spbpers_sex = 'M' AND(months_between(NVL(spbpers_dead_date,sysdate),spbpers_birth_date)/12) < 18; BEGIN FOR i IN cur LOOP IF i.spriden_mi IS NOT NULL THEN dbms_output.put_line(i.spriden_pidm || ' ' || i.spriden_first_name || ' ' || SUBSTR(i.spriden_mi, 1, 1) || '. ' || i.spriden_last_name || ' is younger than 18 years old.'); ELSE dbms_output.put_line(i.spriden_pidm || ' ' || i.spriden_first_name || ' ' || i.spriden_last_name || ' is younger than 18 years old.'); END IF; END LOOP; END; Magic Number Redundant Code Poor Naming
5
Standardized naming conventions make it easier to read code. Examples: [Root_Name]_c for cursors [Root_Name]_r for records [Root_Name]_t for tables of variables
6
Variable names should tell the reader what they are and what they do. 'i and cur' are not descriptive, they are iterators and cursors, but for what. male_student_r, tells you it is a male student’s record. male_student_c, tells you it is a cursor of male students.
7
Replace magic numbers in code with constants Improves readability by saying what it is Makes maintenance quick and easy by updating one piece of code, all references are changed This can also be done with character codes to improve readability DECLARE MAX_AGE CONSTANT PLS_INTEGER := 18; MAX_WAIT_TIME CONSTANT PLS_INTEGER := 15 * 60; /*In seconds*/ MALE CONSTANT CHAR := 'M';...
8
Extract logic into functions and procedures to simplify code flow Remove duplicate code FUNCTION format_name( First_Name VARCHAR2, Middle_Name VARCHAR2, Last_Name VARCHAR2) RETURN VARCHAR2 AS Full_Name VARCHAR2(4000) := First_Name; BEGIN IF Middle_Name IS NOT NULL THEN Full_Name := Full_Name || ' ' || SUBSTR(Middle_Name, 1, 1) || '.'; END IF; RETURN Full_Name || ' ' || Last_Name; END;
9
DECLARE MAX_AGE CONSTANT PLS_INTEGER := 18; CURSOR male_student_c IS … AND TRUNC(months_between(NVL(spbpers_dead_date, sysdate), spbpers_birth_date) / 12) < MAX_AGE; FUNCTION Format_Name(First_Name VARCHAR2, Middle_Name VARCHAR2, Last_Name VARCHAR2) RETURN VARCHAR2 AS Full_Name VARCHAR2(4000) := First_Name; BEGIN IF Middle_Name IS NOT NULL THEN Full_Name := Full_Name || ' ' || SUBSTR(Middle_Name, 1, 1) || '.'; END IF; RETURN Full_Name || ' ' || Last_Name; END; BEGIN FOR Male_Student_r IN Male_Student_c LOOP dbms_output.put_line( Format_Name(First_Name => Male_Student_r.spriden_first_name, Middle_Name => Male_Student_r.spriden_mi, Last_Name => Male_Student_r.spriden_last_name) || ' is younger than ' || MAX_AGE || ' years old.'); END LOOP; END; Removed Magic Number Extracted Code Descriptive Names
10
Be useful DECLARE /*Cursor containing names of all male students under MAX_AGE*/ CURSOR male_student_c IS SELECT spriden_first_name, spriden_last_name, spriden_mi FROM spriden, spbpers WHERE spriden_change_ind IS NULL AND spriden_pidm = spbpers_pidm AND SPBPERS_SEX = 'M' AND(months_between(NVL(spbpers_dead_date, sysdate), spbpers_birth_date) / 12) < MAX_AGE ;
11
…not redundant /* For ever record in cursor */ for i in cur loop … /* If there is no middle name */ if i.spriden_mi is not null then … /* Open the cursor */ open a_cursor; … Duh
12
Records Container for data No Subprograms Package or PL/SQL Block Scope Objects Container for data May Contain Subprograms Schema Scope Encapsulates date Provide abstraction 2 kinds in PL/SQL
13
Useful for encapsulating many related values Makes passing many values clean and concise Only usable within the PL/SQL block that defined them
14
DECLARE Type student IS record ( first_name spriden.spriden_first_name%type, middle_name spriden.spriden_mi%type, last_name spriden.spriden_last_name%type, age PLS_INTEGER := 0) ; … BEGIN …
15
DECLARE type student IS record ( First_Name spriden.spriden_first_name%type, Middle_Name spriden.spriden_mi%type, Last_Name spriden.spriden_last_name%type, Age PLS_INTEGER := 0) ; student_r student; BEGIN student_r.First_Name := 'First'; student_r.Middle_Name := 'Middle'; student_r.Last_Name := 'Last'; dbms_output.put_line(student_r.First_Name || ' ' || student_r.Middle_Name || ' ' || student_r.Last_Name || ' is ' || student_r.Age || ' years old.') ; END;
16
DECLARE type student … student_r student; BEGIN SELECT spriden_first_name, spriden_last_name, spriden_mi, TRUNC(months_between(NVL(spbpers_dead_date,sysdate),spbpers_birth_date)/12) age INTO student_r FROM spriden, spbpers WHERE spriden_change_ind IS NULL AND spriden_pidm = spbpers_pidm AND spriden_pidm = 1234; dbms_output.put_line(student_r.first_name||' is '||student_r.age||' Years old.'); END;
17
DECLARE type student … student_r student; FUNCTION format_name(student_r student) RETURN VARCHAR2 AS Full_Name VARCHAR2(4000) := student_r.First_Name; BEGIN IF student_r.Middle_Name IS NOT NULL THEN Full_Name := Full_Name || ' ' || SUBSTR(student_r.Middle_Name, 1, 1) || '.'; END IF; RETURN Full_Name || ' ' || student_r.Last_Name; END; BEGIN SELECT … INTO student_r FROM spriden, spbpers WHERE … dbms_output.put_line(Format_Name(student_r)||' is '||student_r.Age||' years old.'); END;
18
Useful for encapsulating many related values Makes passing many values clean and concise May contain logic to operate on the stored data Useable anywhere in the database Can't define types off of table columns
19
CREATE OR REPLACE TYPE student AUTHID CURRENT_USER AS OBJECT ( first_name VARCHAR2(256), middle_name VARCHAR2(256), last_name VARCHAR2(256), birth_date DATE, dead_date DATE, MEMBER FUNCTION format_name RETURN VARCHAR2, MEMBER FUNCTION get_age RETURN pls_integer, CONSTRUCTOR FUNCTION student( self IN OUT nocopy student, pidm NUMBER) RETURN SELF AS RESULT);
20
CREATE OR REPLACE TYPE BODY student AS MEMBER FUNCTION format_name RETURN VARCHAR2 IS full_name VARCHAR2(4000) := first_name; BEGIN IF middle_name IS NOT NULL THEN full_name := full_name || ' ' || SUBSTRC(middle_name, 1, 1) || '.'; END IF; RETURN full_name || ' ' || last_name; END; MEMBER FUNCTION get_age RETURN pls_integer IS BEGIN RETURN TRUNC(months_between( NVL(dead_date, sysdate), birth_date) / 12) ; END; CONSTRUCTOR FUNCTION student( self IN OUT nocopy student, pidm NUMBER) RETURN SELF AS RESULT AS BEGIN SELECT spriden_first_name, spriden_mi, spriden_last_name, spbpers_birth_date, spbpers_dead_date INTO self.first_name, self.middle_name, self.last_name, self.birth_date, self.dead_date FROM spriden, spbpers WHERE spriden_change_ind IS NULL AND spriden_pidm = spbpers_pidm AND spriden_pidm = pidm; RETURN; END;
21
DECLARE student_r student; BEGIN student_r := student(pidm => 1234) ; dbms_output.put_line(student_r.format_name || ' is ' || student_r.get_age || ' Years old.') ; END;
22
DECLARE student_r student; BEGIN SELECT student(pidm => spriden_pidm) INTO student_r FROM spriden WHERE spriden_change_ind IS NULL AND spriden_pidm = 1234; dbms_output.put_line(student_r.format_name || ' is ' || student_r.get_age || ' Years old.') ; END;
23
Notifying the calling code something exceptional happened Errors aren’t hidden in a return value Allows calling code to handle the exceptional event as it sees fit Very useful for Packages
24
CREATE OR REPLACE PACKAGE gyk_email AUTHID DEFINER AS EXP_UNKNOWN_EMAIL EXCEPTION; DEFAULT_FROM_ADDRESS CONSTANT VARCHAR2(20 CHAR) := 'noreply@cuesta.edu'; SMTP_SERVER CONSTANT VARCHAR2(512) := '127.0.0.1'; PROCEDURE set_subject(subject VARCHAR2); PROCEDURE set_body(email_body VARCHAR2); PROCEDURE add_to(email VARCHAR2 DEFAULT NULL, pidm spriden.Spriden_pidm%type DEFAULT NULL, id spriden.spriden_id%type DEFAULT NULL, user_name VARCHAR2 DEFAULT NULL); PROCEDURE send_email;... END gyk_email;
25
FUNCTION get_email_address( email VARCHAR2 DEFAULT NULL, pidm spriden.Spriden_pidm%type DEFAULT NULL, id spriden.spriden_id%type DEFAULT NULL, user_name VARCHAR2 DEFAULT NULL) RETURN GOREMAL.GOREMAL_EMAIL_ADDRESS%type AS email_address GOREMAL.GOREMAL_EMAIL_ADDRESS%type; BEGIN... [Logic to get email address based off of given information]... IF email_address IS NULL THEN RAISE EXP_UNKNOWN_EMAIL; END IF; RETURN email_address; END; PROCEDURE add_to( email VARCHAR2 DEFAULT NULL, pidm spriden.Spriden_pidm%type DEFAULT NULL, id spriden.spriden_id%type DEFAULT NULL, user_name VARCHAR2 DEFAULT NULL) AS email_address GOREMAL.GOREMAL_EMAIL_ADDRESS%type; BEGIN /* The exception raised by get_email_address is not caught in this block */ email_address := get_email_address(email => email, pidm => pidm, id => id, user_name => user_name); add_email(to_addresses, email_address); END;
26
DECLARE DEFAULT_EMAIL CONSTANT VARCHAR2(20):= 'mowen@cuesta.edu'; BEGIN /* May raise an exception if there is no email address associated with the current user in banner ex BANINST1 has no email address */ GYK_EMAIL.ADD_TO(user_name => USER); EXCEPTION WHEN GYK_EMAIL.EXP_UNKNOWN_EMAIL THEN /* If no email address can be found, send to a default email address */ GYK_EMAIL.ADD_TO(email => DEFAULT_EMAIL ); END;
27
Poor reusability Forces the failure to have only one outcome IF email_address IS NULL THEN email_address := DEFAULT_EMAIL; END IF; RETURN email_address;
28
DECLARE TYPE student_t IS TABLE OF student; student_indx PLS_INTEGER; failed_student_indx PLS_INTEGER; students student_t := student_t(student(7366), student (1234)); failed_students student_t := student_t(); BEGIN student_indx := students.first; LOOP exit when student_indx is null; BEGIN GYK_EMAIL.ADD_TO( pidm=>students(student_indx).pidm); EXCEPTION WHEN GYK_EMAIL.EXP_UNKNOWN_EMAIL THEN failed_students.extend(1); failed_students(failed_students.COUNT):= students(student_indx); END; student_indx := students.next(student_indx); END LOOP; failed_student_indx := failed_students.first; LOOP exit when student_indx is null; /* Act on failed_students; */ failed_student_indx := failed_students.next(failed_student_indx); END LOOP; END; Waits until all students are processed then processes the errors
29
Only raise an exception when it can be handled by PL/SQL Avoid using exceptions when writing PL/SQL that will be called from the SQL engine CREATE OR REPLACE FUNCTION RAISE_EXCEPTION (PIDM IN SPRIDEN.SPRIDEN_PIDM%TYPE) RETURN VARCHAR2 AUTHID definer AS no_middle_name exception; BEGIN if pidm = 7366 then raise no_middle_name; else return null; end if; END RAISE_EXCEPTION; select * from spriden where RAISE_EXCEPTION(spriden_pidm) is null … ORA-06510: PL/SQL: unhandled user-defined exception
30
Tests that check the functionality of a single unit of code Should be automatable Must be consistent Allows the quick isolation of bugs in regression testing
31
CREATE OR REPLACE FUNCTION FORMAT_NAME ( FIRST_NAME IN VARCHAR2, MIDDLE_NAME IN VARCHAR2, LAST_NAME IN VARCHAR2 ) RETURN VARCHAR2 AUTHID DEFINER AS Full_Name VARCHAR2(4000) := NLS_INITCAP(First_Name); BEGIN IF Middle_Name IS NOT NULL THEN Full_Name := Full_Name || ' ' || Upper(SUBSTR(Middle_Name, 1, 1)) || '.'; END IF; RETURN Full_Name || ' ' || NLS_INITCAP(Last_Name); END FORMAT_NAME;
33
Write a unit test Write code to pass unit test Repeat
34
Fully tested code Easy regression testing Magic button that tells you everything is correct
35
Inefficient data types Not using indexes Truncating indexed date columns Implicit data type conversion on indexed columns Functions Cursor loops Concatenation instead of Tuples
36
The NUMBER data type is a 176 bit number capable of ranges between ± 10E125 with a precision of 1E-130 PLS_INTEGER is a 32 bit integer capable of ranges between -2^31 to 2^31 Operations on PLS_INTEGERS are about 3 times faster than on NUMBER and uses 20% the space If you only need integers, use PLS_INTEGER
37
DECLARE /*Bubble Sort*/ start_time PLS_INTEGER := dbms_utility.get_time; TYPE PLS_INTEGER_t IS TABLE OF [PLS_INTEGER or NUMBER]; collection PLS_INTEGER_t ; swap PLS_INTEGER; made_swap BOOLEAN := false; BEGIN SELECT RANDOM_VALUES_NUMBER BULK COLLECT INTO collection FROM RANDOM_VALUES; LOOP made_swap := false; FOR collection_indx IN 2.. collection.last LOOP IF collection(collection_indx - 1) > collection(collection_indx) THEN swap := collection(collection_indx) ; collection(collection_indx) := collection(collection_indx - 1) ; collection(collection_indx - 1) := swap; made_swap := true; END IF; END LOOP; EXIT WHEN made_swap = false; END LOOP; DBMS_OUTPUT.PUT_LINE((dbms_utility.get_time - start_time)* 10 || ' ms'); END; PLS_INTEGER: 2850 ms NUMBER: 3830 ms
38
Indexes help speed up data retrieval Only if the SQL Engine can use them Only if the index is the exact same as the predicate in the where block
39
SQLPlanTime SELECT * FROM SORTEST WHERE TRUNC(SORTEST_TEST_DATE) = TO_DATE('10-01-2013','mm-dd-yyyy') SELECT STATEMENT TABLE ACCESS (FULL) Filter Predicates TRUNC(INTERNAL_FUNCTION( SORTEST_TEST_DATE))=TO_DATE(...) 866 ms SELECT * FROM SORTEST WHERE SORTEST_TEST_DATE >= TO_DATE('10-01-2013','mm-dd-yyyy') AND SORTEST_TEST_DATE < TO_DATE('10-02-2023','mm-dd-yyyy'); SELECT STATEMENT TABLE ACCESS (BY INDEX ROWID) INDEX (RANGE SCAN) Access Predicates AND SORTEST_TEST_DATE >=TO_DATE(…) SORTEST_TEST_DATE <TO_DATE(…) 5 ms
40
SQLPlanTime SELECT * FROM SSBSECT WHERE SSBSECT_TERM_CODE = 201203; SELECT STATEMENT TABLE ACCESS (FULL) Filter Predicates TO_NUMBER(SSBSECT_TERM_CODE)= 201203 24 ms SELECT * FROM SSBSECT WHERE SSBSECT_TERM_CODE = '201203'; SELECT STATEMENT TABLE ACCESS (BY INDEX ROWID) INDEX (SCAN SKIP) Access Predicates SSBSECT_TERM_CODE = '201203' Filter Predicates SSBSECT_TERM_CODE = '201203' 7 ms
41
SQLPlanTime select * from SPBPERS where SUBSTR(SPBPERS_SSN,6,4)='1234' SELECT STATEMENT TABLE ACCESS (FULL) Filter Predicates SUBSTR(SPBPERS_SSN,6,4) = '1234' 360ms create INDEX indx ON spbpers (substr(spbpers_ssn,6,4)); select * from SPBPERS where SUBSTR(SPBPERS_SSN,6,4)='1234' SELECT STATEMENT TABLE ACCESS (BY INDEX ROWID) INDEX (SCAN SKIP) Access Predicates SUBSTR(SPBPERS_SSN,6,4)='1234' 60 ms
42
Runs in 10,070 ms, should take 10,000 ms Each iteration has overhead ~70μs per Runs in 11,200 ms Doesn't actually do any work 11 seconds of overhead ~43μs per FOR spriden_r IN (SELECT * FROM spriden WHERE rownum <= 1000) LOOP dbms_lock.sleep(0.01); /* 10ms */ END LOOP; FOR spriden_r IN (SELECT * FROM spriden) /*~250k records*/ LOOP NULL; END LOOP;
43
What's going on? Context switching A cursor is created in the SQL Engine for the select statement Each iteration of the loop in the PL/SQL Engine has to request the next record of the cursor from the SQL Engine Each request takes about 50 μs, across hundreds of thousands, or millions of records, it adds up
44
Fetch more than one record at a time Reduces the fetches, reduces the overhead Fetch SizeOverhead Reduction 10% 580% 1090% 10099% 100099.9%
45
Why not all of them at once? Memory use increases by the inverse factor Fetch SizeOverhead ReductionMemory Growth 10%1x 580%5x 1090%10x 10099%100x 100099.9%1,000x
46
How to store the records Declare a new Type to be a table of either a Rowtype, a Record, or an Object DECLARE TYPE spriden_t IS TABLE OF spriden%rowtype INDEX BY PLS_INTEGER; TYPE student_t IS TABLE OF student INDEX BY PLS_INTEGER; students_table student_t; spriden_table spriden_t; BEGIN … END;
47
DECLARE FETCH_LIMIT CONSTANT PLS_INTEGER := 50; CURSOR spriden_c IS SELECT * FROM spriden; TYPE spriden_t IS TABLE OF spriden%rowtype INDEX BY PLS_INTEGER; student_collection spriden_t; BEGIN OPEN spriden_c; LOOP FETCH spriden_c BULK COLLECT INTO student_collection LIMIT FETCH_LIMIT ; FOR student_indx IN 1.. student_collection.COUNT LOOP NULL; /* Do Work */ END LOOP; EXIT WHEN student_collection.COUNT < FETCH_LIMIT ; END LOOP; CLOSE spriden_c; END;
48
select * from table1 where CHARS || NUMS in (select CHARS || NUMS from table2 where NUMS = 50); select * from table1 where (CHARS, NUMS) in (select CHARS, NUMS from table2 where NUMS = 50); Explain Plan | Operation | Rows | Bytes | --------------------------------------- | SELECT STATEMENT | 1029K| 48M| | HASH JOIN | 1029K| 48M| | VIEW | 970 | 31040 | | HASH UNIQUE | 970 | 24250 | | INDEX RANGE SCAN| 970 | 24250 | | TABLE ACCESS FULL | 106K| 1761K| Explain Plan | Operation | Rows | Bytes | ------------------------------------- | SELECT STATEMENT | 970 | 40740 | | HASH JOIN SEMI | 970 | 40740 | | INDEX RANGE SCAN| 994 | 16898 | | INDEX RANGE SCAN| 970 | 24250 | ConcatenationTuple
49
There is an index for Chars, and Nums There is no index for their concatenation It must look at every row and concatenate Nums to Chars, then compare
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.