Copyright All material contained herein is owned by Daniel Stober, the author of this presentation. This presentation and the queries, examples, and original.

Slides:



Advertisements
Similar presentations
BD05/06 PL/SQL  Introduction  Structure of a block  Variables and types  Accessing the database  Control flow  Cursors  Exceptions  Procedures.
Advertisements

Oracle PL/SQL IV Exceptions Packages.
AN INTRODUCTION TO PL/SQL Mehdi Azarmi 1. Introduction PL/SQL is Oracle's procedural language extension to SQL, the non-procedural relational database.
PL/SQL. Introduction to PL/SQL PL/SQL is the procedure extension to Oracle SQL. It is used to access an Oracle database from various environments (e.g.
Chapter 4B: More Advanced PL/SQL Programming
PL/SQL (Embedded SQL) Introduction Benefits Basic Constructs
PL/SQL Agenda: Basic PL/SQL block structure
1 PL/SQL programming Procedures and Cursors Lecture 1 Akhtar Ali.
PL/SQL Bulk Collections in Oracle 9i and 10g Kent Crotty Burleson Consulting October 13, 2006.
Introduction to PL/SQL. Procedural Language extension for SQL Oracle Proprietary 3GL Capabilities Integration of SQL Portable within Oracle data bases.
Bordoloi and Bock CURSORS. Bordoloi and Bock CURSOR MANIPULATION To process an SQL statement, ORACLE needs to create an area of memory known as the context.
PL / SQL P rocedural L anguage / S tructured Q uery L anguage Chapter 7 in Lab Reference.
Bordoloi and Bock EXCEPTIONS. Bordoloi and Bock Errors Two types of errors can be found in a program: compilation errors and runtime errors. There is.
Cursor and Exception Handling By Nidhi Bhatnagar.
Oracle10g Developer: PL/SQL Programming1 Objectives Manipulating data with cursors Managing errors with exception handlers Addressing exception-handling.
EE Copyright س Oracle Corporation, All rights reserved. ® Review of PL/SQL.
Lecture 4 PL/SQL language. PL/SQL – procedural SQL Allows combining procedural and SQL code PL/SQL code is compiled, including SQL commands PL/SQL code.
Program with PL/SQL. Interacting with the Oracle Server.
Overview · What is PL/SQL · Advantages of PL/SQL · Basic Structure of a PL/SQL Block · Procedure · Function · Anonymous Block · Types of Block · Declaring.
1 Theory, Practice & Methodology of Relational Database Design and Programming Copyright © Ellis Cohen Cursors These slides are licensed under.
1. 1. Which type of argument passes a value from a procedure to the calling program? A. VARCHAR2 B. BOOLEAN C. OUT D. IN 2.
PL SQL Block Structures. What is PL SQL A good way to get acquainted with PL/SQL is to look at a sample program. PL/SQL combines the data manipulating.
In Oracle.  A PL/SQL block stored in the database and fired in response to a specified event ◦ DML statements : insert, update, delete ◦ DDL statements.
CS178 Database Management PL/SQL session 8 References: ORACLE 9i PROGRAMMING A Primer Rajshekhar Sunderraman.
1 CursorsCursors. 2 SQL Cursor A cursor is a private SQL work area. A cursor is a private SQL work area. There are two types of cursors: There are two.
PL/SQL Oracle's Database Programming Language. Remember: Set serveroutput on With serveroutput off (default) executing procedure: With serveroutput on:
CIS4368: Advanced DatabaseSlide # 1 PL/SQL Dr. Peeter KirsSpring, 2003 PL/SQL.
Trapping Oracle Server Exceptions. 2 home back first prev next last What Will I Learn? Describe and provide an example of an error defined by the Oracle.
PL/SQL Block Structure DECLARE - Optional Variables, cursors, user-defined exceptions BEGIN - Mandatory SQL Statements PL/SQL Statements EXCEPTIONS - Optional.
Chapter 15 Introduction to PL/SQL. Chapter Objectives  Explain the benefits of using PL/SQL blocks versus several SQL statements  Identify the sections.
Using SQL in PL/SQL ITEC 224 Database Programming.
1 Handling Exceptions Part F. 2 Handling Exceptions with PL/SQL What is an exception? Identifier in PL/SQL that is raised during execution What is an.
1 Theory, Practice & Methodology of Relational Database Design and Programming Copyright © Ellis Cohen Subqueries These slides are licensed under.
Introduction to Explicit Cursors. 2 home back first prev next last What Will I Learn? Distinguish between an implicit and an explicit cursor Describe.
Chapter 18: Exception Handling1 Chapter Eighteen Exception Handling Objective: – Define exceptions – List types of exception handlers – Trap errors – Exception.
1 PL/SQL Part C Scope and Interacting with the Oracle Server.
Introduction to PL/SQL Francis Thottungal. The outline The basic PL/SQL code structure is : DECLARE -- optional, which declares and define variables,
Advanced SQL: Cursors & Stored Procedures Instructor: Mohamed Eltabakh 1.
Copyright  All material contained herein is owned by Daniel Stober, the author of this presentation. This presentation and the queries, examples, and.
CSCI N311: Oracle Database Programming 5-1 Chapter 15: Changing Data: insert, update, delete Insert Rollback Commit Update Delete Insert Statement –Allows.
Kingdom of Saudi Arabia Ministry of Higher Education Al-Imam Muhammad Ibn Saud Islamic University College of Computer and Information Sciences Overview.
CS422 Principles of Database Systems Oracle PL/SQL Chengyu Sun California State University, Los Angeles.
ITEC 224 Database Programming
CS322: Database Systems PL/ SQL PL/SQL by Ivan Bayross.
Oracle SQL.
Interacting with the Oracle8 Server
Interacting with the Oracle Server
Interacting with the Oracle Server
Creating Stored Procedures and Functions
Interacting with the Oracle Server
PL/SQL MULTIPLE CHOICE QUESTION.
Oracle11g: PL/SQL Programming Chapter 4 Cursors and Exception Handling.
Oracle9i Developer: PL/SQL Programming Chapter 3 PL/SQL Processing.
Handling Exceptions.
SQL PL/SQL Presented by: Dr. Samir Tartir
Database Management Systems 2
Interacting with the Oracle Server
كتابة الجمل التنفيذية في PL/SQL
Handling Exceptions.
Chapter 2 Handling Data in PL/SQL Blocks Oracle9i Developer:
Advanced SQL: Views & Triggers
Database Management Systems 2
Handling Exceptions.
PL/SQL week10.
PRACTICE OVERVIEW PL/SQL Part - 1.
Database Programming Using Oracle 11g
Database Programming Using Oracle 11g
Database Programming Using Oracle 11g
Database Programming Using Oracle 11g
Database Programming Using Oracle 11g
Presentation transcript:

Copyright All material contained herein is owned by Daniel Stober, the author of this presentation. This presentation and the queries, examples, and original sample data may be shared with others for educational purposes only in the following circumstances: That the person or organization sharing the information is not compensated, or If shared in a circumstance with compensation, that the author has granted written permission to use this work Using examples from this presentation, in whole or in part, in another work without proper source attribution constitutes plagiarism. Use of this work implies acceptance of these terms

Six Bugs you can put into Your PLSQL Programs Dan Stober Intermountain Healthcare September 14, 2011

Dan Stober Data Architect – Intermountain Healthcare Attended California State Univ., Fresno Working in Oracle databases since 2001 Frequent presenter at local and national user group conferences Oracle Open World twice Private Instructor for Trutek Teaching PLSQL Oracle Certified SQL Expert Board of Trustees – Utah Oracle Users Group Edit newsletter Write SQL Tip column

Intermountain Healthcare 23 hospitals in Utah and Idaho Non-profit integrated health care system 750 Employed physicians 32,000 employees The largest non-government employer in Utah One of the largest and most complete clinical data warehouses in the world!

Session Norms Questions? I learn something from every session I do! Interrupt Me! I learn something from every session I do! Set the record straight! Cell phones? OK!

Bugs? These are: How did I learn these? My goal? Not bugs in PLSQL Errors in the code introduced by programmers who missed some of the nuances in the language Examples of PLSQL behaving exactly as documented How did I learn these? I’ve made every one of these mistakes! My goal? Make you aware So you can avoid them

Bug Agenda FOR LOOP variables No_data_found Parameter defaults Function returned without value Declaration section exceptions More FOR LOOP issues Implicit COMMIT

Bug #1 FOR LOOP Variables

What will be the result? This variable i is the same as this i DECLARE i NUMBER := 10; BEGIN dbms_output.put_line ( 'i=' || i ) ; FOR i IN 1..5 LOOP END LOOP; END; This variable i is the same as this i But, this i is a different variable i=10 i=1 i=2 i=3 i=4 i=5 PL/SQL procedure successfully completed.

The FOR LOOP handles all of these Components of a LOOP Duties / Functions Declaration Initialization Iteration Exit condition DECLARE i NUMBER; BEGIN i := 1; LOOP dbms_output.put_line ( i ) ; i := i + 1; EXIT WHEN i > 5; END LOOP; END; Declaration Initialization Iteration Exit Condition The FOR LOOP handles all of these BEGIN FOR i IN 1..5 LOOP dbms_output.put_line ( i ) ; END LOOP; END;

Components of a Cursor Loop DECLARE CURSOR emps IS SELECT ename FROM scott.emp; rec emps%TYPE; BEGIN OPEN emps; LOOP FETCH emps INTO rec; EXIT WHEN emps%NOTFOUND; dbms_output.put_line ( rec.ename ) END LOOP; CLOSE emps; END; Declaration Open / Close Record Fetch Iteration Exit Condition Open / Close

Components of a Cursor FOR Loop Declaration BEGIN FOR rec IN ( SELECT ename FROM scott.emp ) LOOP dbms_output.put_line ( rec.ename ) END LOOP; END; Open / Close Record Fetch Iteration Exit Condition

How does this manifest as a bug? Scope of LOOP variables DECLARE CURSOR emps IS SELECT empno, ename FROM scott.emp; rec emps%TYPE; BEGIN FOR rec IN emps LOOP INSERT INTO myemps VALUES rec; END LOOP; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM || '>' || rec.empno ); END; It creates the temptation to reference the loop variable outside of the loop, with the belief that the variable will retain the value that it held inside of the loop.

CURSOR FOR variables The same principle applies in a CURSOR FOR LOOP DECLARE CURSOR emps_cur is ( SELECT empno, ename, sal FROM emp ORDER BY empno); rec emps_cur%TYPE; BEGIN FOR rec IN emps_cur LOOP process_record ( rec.empno ); END LOOP; IF rec.sal > 1000 THEN ... etc etc etc The programmer probably intended to read the value from the last record processed. But this is not the same variable

One solution Bug #1: Never declare FOR LOOP variables On solution: Create a separate variable With a different name With scope outside of the loop DECLARE CURSOR emps IS SELECT empno, ename FROM scott.emp; v_empno NUMBER; BEGIN FOR rec IN emps LOOP v_empno := rec.empno; INSERT INTO myemps VALUES rec; END LOOP; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM || '>' || v_empno ); END; Assign the value from the cursor record to a variable with a scope which exceeds the loop boundary.

Bug #2 No_data_found

No_data_found Built-in PLSQL exception designed to be used with implicit cursors Implicit cursor: Query structure using SELECT xxx INTO xxx One and only one record is expected If more than one record: Too_many_rows If no records: No_data_found Bug: An aggregate function always returns one row Unless used with GROUP BY

Some handlers are not needed Group functions always return one row SQL> SELECT COUNT(*) 2 FROM emp 3 WHERE job = 'DBA'; COUNT(*) --------- 1 row selected. FUNCTION get_emp_job_count ( p_job IN VARCHAR2 ) RETURN NUMBER IS v_ret_val NUMBER; BEGIN SELECT COUNT(*) INTO v_ret_val FROM emp WHERE job = p_job; EXCEPTION WHEN no_data_found THEN v_ret_val := 0; END; RETURN v_ret_val; So what? The handler isn’t needed. It might be extra clutter, but does that make it a bug? This exception will never be raised

This exception will never be raised Unused handler FUNCTION get_job_sal_sum ( p_job IN VARCHAR2 ) RETURN NUMBER IS v_ret_val NUMBER; BEGIN SELECT SUM( sal ) INTO v_ret_val FROM emp WHERE job = p_job; EXCEPTION WHEN no_data_found THEN v_ret_val := 0; END; RETURN v_ret_val; The attempt here is to return ZERO when there are no emps with the job This exception will never be raised When called for a JOB with no records in the table, the function will return NULL. Not ZERO.

Calling the Buggy Function BEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN')); END; / CLERK: 4150 DBA: SALESMAN: 5600 PL/SQL procedure successfully completed. BEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN')); END; The function returned NULL The query DID return a record, thus no_data_found was not raised, so the function returned the value from the query. It did not get replaced with ZERO. Here’s the query that was executed on the second call SQL> SELECT SUM ( sal ) 2 FROM emp 3 WHERE job = 'DBA'; SUM(SAL) ---------- 1 row selected.

Fixing the Buggy Function FUNCTION get_job_sal_sum ( p_job IN VARCHAR2) RETURN NUMBER IS v_ret_val NUMBER; BEGIN SELECT SUM( sal ) INTO v_ret_val FROM emp WHERE job = p_job; RETURN NVL ( v_ret_val, 0); END; If the variable contains NULL, it will return ZERO instead BEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN')); END; BEGIN dbms_output.put_line ( 'CLERK: ' || get_job_sal_sum ( 'CLERK')); dbms_output.put_line ( 'DBA: ' || get_job_sal_sum ( 'DBA')); dbms_output.put_line ( 'SALESMAN: ' || get_job_sal_sum ( 'SALESMAN')); END; / CLERK: 4150 DBA: 0 SALESMAN: 5600 PL/SQL procedure successfully completed. Excellent!

Another Way to Fix It FUNCTION get_emp_job_count ( p_job IN VARCHAR2 ) RETURN NUMBER IS v_ret_val NUMBER; BEGIN SELECT COUNT(*) INTO v_ret_val FROM emp WHERE job = p_job GROUP BY job; EXCEPTION WHEN no_data_found THEN v_ret_val := 0; END; RETURN v_ret_val; With a GROUP BY, the query will return no records, if there is no match! SQL> SELECT COUNT(*) 2 FROM emp 3 WHERE job = 'DBA' 4 GROUP BY job; no rows selected

Another issue Do you see it? PROCEDURE process_sal_increase ( p_empno IN NUMBER ) IS v_current_sal NUMBER; BEGIN SELECT sal INTO v_current_sal FROM emp WHERE empno = p_empno AND ROWNUM = 1; v_new_sal := calculate_new_sal ( p_empno, v_sal ); END; If the WHERE clause returns more than one record, and you don’t care which one you get, then why bother selecting? - Tom Kyte, paraphrased Why is this here? When you see this, it usually means that too_many_rows was raised at some point. Instead of fixing the data or the WHERE clause, the developer added this

An Aside: Performance Bug DECLARE V_rec_cnt INTEGER; BEGIN SELECT COUNT(*) INTO V_rec_cnt FROM load_table; IF V_rec_cnt > 0 THEN -- table is not empty, process records . . . What is the purpose? Does the program really need to know how many records are in the table? Or, is the intent just to make sure that the table is not empty? SELECT COUNT(*) INTO V_rec_cnt FROM load_table WHERE ROWNUM = 1;

Opens cursor to find out how many records will be returned Another variation SELECT empi, fcilty_id, immu_dt , COUNT (*) OVER () rec_count FROM immu_vaccinations WHERE acct_no = '7623725' DECLARE V_rec_count : CURSOR my_recs IS ( SELECT empi, fcilty_id, immu_dt FROM immu_vaccinations WHERE acct_no = '7623725' )   recs_t IS TABLE OF my_recs%ROWTYPE; v_recs recs_t; BEGIN OPEN my_recs; FETCH my_recs BULK COLLECT INTO v_recs; CLOSE my_recs; IF v_recs.COUNT = 1 THEN FOR j IN my_recs LOOP << processing to do if the cursor returned only one records >> END LOOP; ELSE << processing to do if the cursor returned multiple records >> END IF; END; Use analytic function to collect the record count at the same time as query records Opens cursor to find out how many records will be returned Different logic for one record versus multiple records. Reexecutes the same query

Bug #3 Default Values for Parameters

Function to calculate age Default value for second parameter means if no value is passed, then function will use sysdate FUNCTION get_age_in_years ( p_birth_date IN DATE , p_as_of_date IN DATE DEFAULT SYSDATE ) RETURN NUMBER IS v_ret_val NUMBER; BEGIN v_ret_val := TRUNC( MONTHS_BETWEEN ( p_as_of_date, p_birth_date )/12); RETURN v_ret_val; END get_age_in_years; BEGIN dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' ) ); END; BEGIN dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' ) ); END; / AGE=43 PL/SQL procedure successfully completed. BEGIN dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); END; / BEGIN dbms_output.put_line ( 'AGE=' || get_age_in_years ( DATE '1968-03-09' , DATE '2011-09-14' ) ); END; / AGE=43 PL/SQL procedure successfully completed.

Parameter Defaults and NULL DECLARE v_from_date DATE; v_to_date DATE; BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date, v_to_date ) ); ( 'AGE=' || get_age_in_years ( v_from_date ) ); END; / AGE= AGE=43 DECLARE v_from_date DATE; v_to_date DATE; BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date, v_to_date ) ); ( 'AGE=' || get_age_in_years ( v_from_date ) ); END; / In the first call, a variable with a NULL value was passed to the second parameter. In the second call, the second parameter was not referenced at all. In the second call, the default value of the parameter was used by the function.

How can you avoid this? If you need to make sure that the parameter is not null, use a variable This is needed still. Why? FUNCTION get_age_in_years ( p_birth_date IN DATE , p_as_of_date IN DATE DEFAULT SYSDATE ) RETURN NUMBER IS v_ret_val NUMBER; v_as_of_date DATE; BEGIN v_as_of_date := NVL( p_as_of_date, SYSDATE ); v_ret_val := TRUNC( MONTHS_BETWEEN ( v_as_of_date, p_birth_date )/12); RETURN v_ret_val; END get_age_in_years; Variable for param NVL to handle NULL value

Calling With Function Fixed DECLARE v_from_date DATE; v_to_date DATE; BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date, v_to_date ) ); ( 'AGE=' || get_age_in_years ( v_from_date ) ); END; / AGE=43 DECLARE v_from_date DATE; v_to_date DATE; BEGIN v_from_date := DATE '1968-03-09'; dbms_output.put_line ( 'AGE=' || get_age_in_years ( v_from_date, v_to_date ) ); ( 'AGE=' || get_age_in_years ( v_from_date ) ); END; / Success!

Bug #4 Function returned without value

Function FUNCTION get_age_description ( p_birth_date IN DATE , p_as_of_date IN DATE ) RETURN VARCHAR2 IS v_age NUMBER; BEGIN v_age := TRUNC( MONTHS_BETWEEN ( p_as_of_date, p_birth_date )/12); IF v_age < 18 THEN RETURN 'Minor'; ELSIF v_age = 18 THEN RETURN 'Voting age'; ELSIF v_age = 21 THEN RETURN 'Drinking age'; ELSIF v_age BETWEEN 22 AND 65 THEN RETURN 'Adult'; ELSIF v_age > 66 THEN RETURN 'Eligible for Medicare'; END IF; END get_age_description; SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; * ERROR at line 1: ORA-06503: PL/SQL: Function returned without value ORA-06512: at "SCOTT.GET_AGE_DESCRIPTION", line 30 SELECT get_age_description ( DATE '1993-07-04', SYSDATE ) AS test FROM DUAL; TEST ---------------- Voting age 1 row selected. SELECT get_age_description ( DATE '1993-07-04', SYSDATE ) AS test FROM DUAL; This error means that execution got all the way to the end of the function and never encountered a RETURN statement

2nd attempt Introduction of a variable to hold RETURN value FUNCTION get_age_description ( p_birth_date IN DATE , p_as_of_date IN DATE ) RETURN VARCHAR2 IS v_age NUMBER; v_ret_val VARCHAR2(25); BEGIN v_age := TRUNC( MONTHS_BETWEEN ( p_as_of_date, p_birth_date )/12); IF v_age < 18 THEN v_ret_val := 'Minor'; ELSIF v_age = 18 THEN v_ret_val := 'Voting age'; ELSIF v_age = 21 THEN v_ret_val := 'Drinking age'; ELSIF v_age > 66 THEN v_ret_val := 'Eligible for Medicare'; ELSIF v_age BETWEEN 22 AND 65 THEN v_ret_val := 'Adult'; END IF; RETURN v_ret_val; END get_age_description; Introduction of a variable to hold RETURN value SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; TEST ---------------- 1 row selected. SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; This time, the function returned NULL ELSE One possible fix Single point of RETURN from function

Using CASE FUNCTION get_age_description ( p_birth_date IN DATE , p_as_of_date IN DATE ) RETURN VARCHAR2 IS v_age NUMBER; v_ret_val VARCHAR2(25); BEGIN v_age := TRUNC( MONTHS_BETWEEN ( p_as_of_date, p_birth_date )/12); CASE WHEN v_age < 18 THEN v_ret_val := 'Minor'; WHEN v_age = 18 THEN v_ret_val := 'Voting age'; WHEN v_age = 21 THEN v_ret_val := 'Drinking age'; WHEN v_age > 66 THEN v_ret_val := 'Eligible for Medicare'; WHEN v_age BETWEEN 22 AND 65 THEN v_ret_val := 'Adult'; END CASE; RETURN v_ret_val; END get_age_description; In any PLSQL development, when you do not expect an “ELSE”, use CASE instead of IF for tighter coding. SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; SELECT get_age_description ( DATE '1992-07-04', SYSDATE ) AS test FROM DUAL; * ERROR at line 1: ORA-06592: CASE not found while executing CASE statement ORA-06512: at "SCOTT.GET_AGE_DESCRIPTION", line 13 Unlike IF /ELSIF, PLSQL CASE requires that one of the branches be executed

Bug #5 Declaration section exceptions

What will happen here? Division by zero here DECLARE x NUMBER :=10/0; BEGIN dbms_output.put_line('Successful run.'); EXCEPTION WHEN zero_divide THEN dbms_output.put_line('zero_divide exception handler'); WHEN OTHERS THEN dbms_output.put_line('others exception handler'); END; / Division by zero here Two different handlers Neither of the exception handlers was raised! DECLARE * ERROR at line 1: ORA-01476: divisor is equal to zero ORA-06512: at line 3

Handlers Gotchas The exception was raised in the “green” block But, it was not handled in “green” block It was handled in “blue” block But, at least it was handled! BEGIN DECLARE v NUMBER(1) := 10; v:= 20; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( 'Handler 1'); END; dbms_output.put_line ( 'Handler 2'); Handler 2

What can you do? Avoid variable assignment in declaration section when possible Not possible with CONSTANT Nest blocks in larger blocks so that errors can be handled

Bug #6 More FOR LOOP problems

The lower number must come first, or the loop is not executed Loop Bugs BEGIN FOR i IN -1..-5 LOOP dbms_output.put_line ( i ); END LOOP; END; No output. Why? BEGIN FOR i IN -5..-1 LOOP dbms_output.put_line ( i ); END LOOP; END; The lower number must come first, or the loop is not executed -5 -4 -3 -2 -1

Even with REVERSE, the lower number must come first Loop Bugs A backwards loop BEGIN FOR i IN REVERSE 5..1 LOOP dbms_output.put_line ( i ); END LOOP; END ; Again: No output. BEGIN FOR i IN REVERSE 1..5 LOOP dbms_output.put_line ( i ); END LOOP; END ; Even with REVERSE, the lower number must come first 5 4 3 2 1

FOR LOOP Endpoints i empno (1) 7369 (2) 7566 (3) 7788 (4) 7876 (5) DECLARE TYPE number_t IS TABLE OF NUMBER; v_empnos number_t; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 20; FOR i IN v_empnos.FIRST..v_empnos.LAST LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos(i) ); END LOOP; END; / i empno (1) 7369 (2) 7566 (3) 7788 (4) 7876 (5) 7902 1 5 7369 7566 7788 7876 7902 PL/SQL procedure successfully completed.

FOR LOOP Endpoints No emps in dept 40 i empno Empty array NULL NULL DECLARE TYPE number_t IS TABLE OF NUMBER; v_empnos number_t; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 40; FOR i IN v_empnos.FIRST..v_empnos.LAST LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos(i) ); END LOOP; END; / No emps in dept 40 i empno Empty array NULL NULL DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error ORA-06512: at line 10

Fix #1 When the array is empty, COUNT returns 0. DECLARE TYPE number_t IS TABLE OF NUMBER; v_empnos number_t; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 40; FOR i IN 1..v_empnos.COUNT LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos(i) ); END LOOP; END; / When the array is empty, COUNT returns 0. It does not return NULL FOR i IN 1..0 LOOP Remember: If the second number is lower than the first number, the FOR LOOP does not execute. PL/SQL procedure successfully completed.

Fix #2 Because it’s not a FOR LOOP, the variable MUST be declared. TYPE number_t IS TABLE OF NUMBER; v_empnos number_t; i NUMBER; BEGIN SELECT empno BULK COLLECT INTO v_empnos FROM scott.emp WHERE deptno = 40; i := v_empnos.FIRST; WHILE i IS NOT NULL LOOP -- Loop through records for additional processing dbms_output.put_line ( v_empnos(i) ); i := v_empnos.NEXT (i); END LOOP; END; / Because it’s not a FOR LOOP, the variable MUST be declared. If the array is empty, this function returns NULL LOOP Exit mechanism When this is called after the last record, it returns NULL, too This is the only way to code this if the array is sparse PL/SQL procedure successfully completed.

Bug #7 Implicit COMMIT

In case of load error, rollback The set-up Clear the table Loop through records EMP_MGR EMP Load Transform Clear BEGIN EXECUTE IMMEDIATE 'TRUNCATE TABLE scott.emp_mgr'; FOR rec IN ( SELECT e.ename AS ename , m.ename AS mgrname FROM scott.emp e JOIN scott.emp m ON e.mgr = m.empno ) LOOP INSERT INTO scott.emp_mgr VALUES rec; END LOOP; COMMIT; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM ); ROLLBACK; END; In case of load error, rollback Load Script to set this up: CREATE TABLE emp_mgr ( ename VARCHAR2(6), mgrname VARCHAR2(6)); INSERT INTO scott.emp VALUES ( 7777, 'O''REILLY', 'CORPORAL', 7902, DATE '1984-06-16', 900, NULL, 10); COMMIT; DESC scott.emp_mgr EMP_MGR ENAME VARCHAR2(6) MGRNAME Rollback

After the error, the table is empty. Something went wrong ETL Run SELECT COUNT(*) FROM scott.emp_mgr; COUNT(*) ---------- 13 1 row selected. INSERT INTO scott.emp VALUES ( 7777, 'O''REILLY', 'CORPORAL' , 7902, DATE '1984-06-16', 900 , NULL, 10); 1 row created. BEGIN EXECUTE IMMEDIATE 'TRUNCATE TABLE scott.emp_mgr'; FOR rec IN ( SELECT e.ename AS ename , m.ename AS mgrname FROM scott.emp e JOIN scott.emp m ON e.mgr = m.empno ) LOOP INSERT INTO scott.emp_mgr VALUES rec; END LOOP; COMMIT; EXCEPTION WHEN OTHERS THEN dbms_output.put_line ( SQLERRM ); ROLLBACK; END; / ORA-12899: value too large for column "SCOTT"."EMP_MGR"."ENAME" (actual: 8, maximum: 6) PL/SQL procedure successfully completed. Now, run ETL procedure After the error, the table is empty. Something went wrong SELECT COUNT(*) FROM scott.emp_mgr; COUNT(*) ---------- 1 row selected.

Why was the load table empty? DDL causes implicit COMMIT TRUNCATE TABLE… CREATE INDEX… Remember that EXECUTE IMMEDIATE… ? This is Oracle database behavior Not PLSQL

Bug Killers Review Checklist : FOR LOOP variables No_data_found Parameter defaults Function returned without value Declaration section exceptions More FOR LOOP issues Implicit COMMIT

Thank-you! Dan Stober Questions? Comments? dan.stober@utoug.org

Copyright All material contained herein is owned by Daniel Stober, the author of this presentation. This presentation and the queries, examples, and original sample data may be shared with others for educational purposes only in the following circumstances: That the person or organization sharing the information is not compensated, or If shared in a circumstance with compensation, that the author has granted written permission to use this work Using examples from this presentation, in whole or in part, in another work without proper source attribution constitutes plagiarism. Use of this work implies acceptance of these terms