INTENDED LEARNING OUTCOME • Determine the SQL statements that can be directly included in a PL/SQL executable block. • Manipulate data with DML statements in PL/SQL. • Use transaction control statements in PL/SQL. • Make use of the INTO clause to hold the values returned by an SQL statement. SQL Statements in PL/SQL You can use the following kinds of SQL statements in PL/SQL: • SELECT statements to retrieve data from a database. • DML statements, such as INSERT, UPDATE, and DELETE, to make changes to the database. • Transaction control statements, such as COMMIT, ROLLBACK, or SAVEPOINT, to make changes to the database permanent or to discard them. 5 DDL/DCL Limitations in PL/SQL • You cannot directly execute DDL and DCL statements because they are constructed and executed at run time— that is, they are dynamic. • There are times when you may need to run DDL or DCL within PL/SQL. • The recommended way of working with DDL and DCL within PL/SQL is to use Dynamic SQL with the EXECUTE IMMEDIATE statement. 6 SELECT Statements in PL/SQL Retrieve data from a database into a PL/SQL variable with a SELECT statement so you can work with the data within PL/SQL. SELECT INTO select_list {variable_name[, variable_name]... | record_name} FROM table [WHERE condition]; 7 Using the INTO Clause • The INTO clause is mandatory and occurs between the SELECT and FROM clauses. • It is used to specify the names of PL/SQL variables that hold the values that SQL returns from the SELECT clause. DECLARE v_emp_lname employees.last_name%TYPE; BEGIN SELECT last_name INTO v_emp_lname FROM employees WHERE employee_id = 100; DBMS_OUTPUT.PUT_LINE('His last name is ' || v_emp_lname); END; 8 Retrieving Data in PL/SQL Example You must specify one variable for each item selected, and the order of the variables must correspond with the order of the items selected. DECLARE v_emp_hiredate employees.hire_date%TYPE; v_emp_salary employees.salary%TYPE; BEGIN hire_date, salary SELECT v_emp_hiredate, v_emp_salary INTO employees FROM WHERE employee_id = 100; DBMS_OUTPUT.PUT_LINE('Hiredate: ' || v_emp_hiredate); DBMS_OUTPUT.PUT_LINE('Salary: '|| v_emp_salary); END; 9 Retrieving Data in PL/SQL Embedded Rule • SELECT statements within a PL/SQL block fall into the ANSI classification of embedded SQL for which the following rule applies: embedded queries must return exactly one row. • A query that returns more than one row or no rows generates an error. DECLARE v_salary employees.salary%TYPE; BEGIN SELECT salary INTO v_salary FROM employees; DBMS_OUTPUT.PUT_LINE(' Salary is : ' || v_salary); END; ORA-01422: exact fetch returns more than requested number of rows 10 Retrieving Data in PL/SQL Example Return the sum of the salaries for all the employees in the specified department. DECLARE v_sum_sal NUMBER(10,2); v_deptno NUMBER NOT NULL := 60; BEGIN SELECT SUM(salary) -- group function INTO v_sum_sal FROM employees WHERE department_id = v_deptno; DBMS_OUTPUT.PUT_LINE('Dep #60 Salary Total: ' || v_sum_sal); END; 11 Create Copy of Original Table • It is very important that you do NOT modify the existing tables (such as EMPLOYEES and DEPARTMENTS), because they will be needed later in the course. • The examples in this lesson use the COPY_EMP table. • If you haven't already created the COPY_EMP table, do so now by executing this SQL statement: CREATE TABLE copy_emp AS SELECT * FROM employees; 13 Manipulating Data Using PL/SQL Make changes to data by using DML commands within your PLSQL block: DELETE • INSERT • UPDATE • DELETE • MERGE INSERT UPDATE MERGE 14 Manipulating Data Using PL/SQL • You manipulate data in the database by using the DML commands. • You can issue the DML commands—INSERT, UPDATE, DELETE, and MERGE—without restriction in PL/SQL. – The INSERT statement adds new rows to the table. – The UPDATE statement modifies existing rows in the table. – The DELETE statement removes rows from the table. 15 Manipulating Data Using PL/SQL • The MERGE statement selects rows from one table to update and/or insert into another table. • The decision whether to update or insert into the target table is based on a condition in the ON clause. – Note: MERGE is a deterministic statement—that is, you cannot update the same row of the target table multiple times in the same MERGE statement. – You must have INSERT and UPDATE object privileges in the target table and the SELECT privilege in the source table. 16 Inserting Data • The INSERT statement adds new row(s) to a table. • Example: Add new employee information to the COPY_EMP table. BEGIN INSERT INTO copy_emp (employee_id, first_name, last_name, email, hire_date, job_id, salary) VALUES (99, 'Ruth', 'Cores', 'RCORES', SYSDATE, 'AD_ASST', 4000); END; • One new row is added to the COPY_EMP table. 17 Updating Data • The UPDATE statement modifies existing row(s) in a table. • Example: Increase the salary of all employees who are stock clerks. DECLARE v_sal_increase employees.salary%TYPE := 800; BEGIN UPDATE copy_emp SET salary = salary + v_sal_increase WHERE job_id = 'ST_CLERK'; END; 18 Deleting Data • The DELETE statement removes row(s) from a table. • Example: Delete rows that belong to department 10 from the COPY_EMP table. DECLARE v_deptno employees.department_id%TYPE := 10; BEGIN DELETE FROM copy_emp WHERE department_id = v_deptno; END; 19 Merging Rows • The MERGE statement selects rows from one table to update and/or insert into another table. • Insert or update rows in the copy_emp table to match the employees table. BEGIN MERGE INTO copy_emp c USING employees e ON (e.employee_id = c.employee_id) WHEN MATCHED THEN UPDATE SET c.first_name = e.first_name, c.last_name = e.last_name, c.email = e.email, . . . WHEN NOT MATCHED THEN INSERT VALUES(e.employee_id, e.first_name,...e.department_id); END; 20 Getting Information From a Cursor • Look again at the DELETE statement in this PL/SQL block. DECLARE employees.department_id%TYPE := 10; v_deptno BEGIN copy_emp DELETE FROM WHERE department_id = v_deptno; END; • It would be useful to know how many COPY_EMP rows were deleted by this statement. • To obtain this information, we need to understand cursors. 21 What is a Cursor? • Every time an SQL statement is about to be executed, the Oracle server allocates a private memory area to store the SQL statement and the data that it uses. • This memory area is called an implicit cursor. • Because this memory area is automatically managed by the Oracle server, you have no direct control over it. • However, you can use predefined PL/SQL variables, called implicit cursor attributes, to find out how many rows were processed by the SQL statement. 23 Implicit and Explicit Cursors There are two types of cursors: • Implicit cursors: Defined automatically by Oracle for all SQL data manipulation statements, and for queries that return only one row. – An implicit cursor is always automatically named “SQL.” • Explicit cursors: Defined by the PL/SQL programmer for queries that return more than one row. 24 Cursor Attributes for Implicit Cursors • Cursor attributes are automatically declared variables that allow you to evaluate what happened when a cursor was last used. • Attributes for implicit cursors are prefaced with “SQL.” • Use these attributes in PL/SQL statements, but not in SQL statements. • Using cursor attributes, you can test the outcome of your SQL statements. 25 Cursor Attributes for Implicit Cursors Attribute Description Boolean attribute that evaluates to TRUE if the SQL%FOUND most recent SQL statement returned at least one row. Boolean attribute that evaluates to TRUE if the SQL%NOTFOUND most recent SQL statement did not return even one row. An integer value that represents the number SQL%ROWCOUNT of rows affected by the most recent SQL statement. 26 Using Implicit Cursor Attributes: Example 1 • Delete rows that have the specified employee ID from the copy_emp table. • Print the number of rows deleted. DECLARE v_deptno copy_emp.department_id%TYPE := 50; BEGIN DELETE FROM copy_emp WHERE department_id = v_deptno; DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' rows deleted.'); END; 27 Using Implicit Cursor Attributes: Example 2 • Update several rows in the COPY_EMP table. • Print the number of rows updated. DECLARE v_sal_increase employees.salary%TYPE := 800; BEGIN UPDATE copy_emp SET salary = salary + v_sal_increase WHERE job_id = 'ST_CLERK'; DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' rows updated.'); END; 28 Using Implicit Cursor Attributes: Good Practice Guideline • Look at this code which creates a table and then executes a PL/SQL block. • Determine what value is inserted into RESULTS. CREATE TABLE results (num_rows NUMBER(4)); BEGIN UPDATE copy_emp SET salary = salary + 100 WHERE job_id = 'ST_CLERK'; INSERT INTO results (num_rows) VALUES (SQL%ROWCOUNT); END; 29 Reference PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell Oracle Academy PLSQL S3L2 Retrieving Data in PL/SQL PLSQL S3L3 Manipulating Data in PL/SQL Copyright © 2019, Oracle and/or its affiliates. All rights reserved. INTENDED LEARNING OUTCOME • Identify the uses and types of control structures. • Construct an IF statement. • Use CASE statements and CASE expressions. • Use guidelines when using conditional control structures. Controlling the Flow of Execution • You can change the logical flow of statements within the PL/SQL block with a number of control structures. • This lesson introduces three types of PL/SQL control structures: – Conditional constructs with the IF statement – CASE expressions – LOOP control structures LOOP FOR WHILE 35 IF Statements Structure • The structure of the PL/SQL IF statement is similar to the structure of IF statements in other procedural languages. • It enables PL/SQL to perform actions selectively based on conditions. • Syntax: IF condition THEN statements; [ELSIF condition THEN statements;] [ELSE statements;] END IF; 36 IF Statements • Condition is a Boolean variable or expression that returns TRUE, FALSE, or NULL. • THEN introduces a clause that associates the Boolean expression with the sequence of statements that follows it. IF condition THEN statements; [ELSIF condition THEN statements;] [ELSE statements;] END IF; 37 Simple IF Statement • This is an example of a simple IF statement with a THEN clause. • The v_myage variable is initialized to 31. DECLARE v_myage NUMBER := 31; BEGIN IF v_myage < 11 THEN DBMS_OUTPUT.PUT_LINE('I am a child'); END IF; END; 38 IF THEN ELSE Statement • The ELSE clause has been added to this example. • The condition has not changed, thus it still evaluates to FALSE. DECLARE v_myage NUMBER:=31; BEGIN IF v_myage < 11 THEN DBMS_OUTPUT.PUT_LINE('I am a child'); ELSE DBMS_OUTPUT.PUT_LINE('I am not a child'); END IF; END; 39 IF ELSIF ELSE Clause • The IF statement now contains multiple ELSIF clauses as well as an ELSE clause. • Notice that the ELSIF clauses add additional conditions. DECLARE v_myage NUMBER := 31; BEGIN IF v_myage < 11 THEN DBMS_OUTPUT.PUT_LINE('I am a child'); ELSIF v_myage < 20 THEN DBMS_OUTPUT.PUT_LINE('I am young'); ELSIF v_myage < 30 THEN DBMS_OUTPUT.PUT_LINE('I am in my twenties'); ELSIF v_myage < 40 THEN DBMS_OUTPUT.PUT_LINE('I am in my thirties'); ELSE DBMS_OUTPUT.PUT_LINE('I am mature'); END IF; END; 40 IF Statement with Multiple Expressions • An IF statement can have multiple conditional expressions related with logical operators, such as AND, OR, and NOT. • This example uses the AND operator. • Therefore, it evaluates to TRUE only if both BOTH the first name and age conditions are evaluated as TRUE. DECLARE v_myage NUMBER := 31; v_myfirstname VARCHAR2(11) := 'Christopher'; BEGIN IF v_myfirstname ='Christopher' AND v_myage < 11 THEN DBMS_OUTPUT.PUT_LINE('I am a child named Christopher'); END IF; END; 41 NULL Values in IF Statements • In this example, the v_myage variable is declared but is not initialized. • The condition in the IF statement returns NULL, which is neither TRUE nor FALSE. • In such a case, the control goes to the ELSE statement because, just NULL is not TRUE. DECLARE v_myage NUMBER; BEGIN IF v_myage < 11 THEN DBMS_OUTPUT.PUT_LINE('I am a child'); ELSE DBMS_OUTPUT.PUT_LINE('I am not a child'); END IF; END; 42 Handling Nulls When working with nulls, you can avoid some common mistakes by keeping in mind the following rules: • Simple comparisons involving nulls always yield NULL. • Applying the logical operator NOT to a null yields NULL. • In conditional control statements, if a condition yields NULL, it behaves just like a FALSE, and the associated sequence of statements is not executed. 43 Handling Nulls Example • In this example, you might expect the sequence of statements to execute because a and b seem equal. • But, NULL is unknown, so we don't know if a and b are equal. • The IF condition yields NULL and the THEN clause is bypassed, with control going to the line following the THEN clause. a.:= NULL; b.:= NULL; ... IF a = b THEN … -- yields NULL, not TRUE and the sequence of statements is not executed END IF; 44 Using a CASE Statement • Look at this IF statement. What do you notice? • All the conditions test the same variable v_numvar. • And the coding is very repetitive: v_numvar is coded many times. DECLARE NUMBER; v_numvar BEGIN ... IF v_numvar = 5 THEN statement_1; statement_2; ELSIF v_numvar = 10 THEN statement_3; ELSIF v_numvar = 12 THEN statement_4; statement_5; ELSIF v_numvar = 27 THEN statement_6; ELSIF v_numvar ... – and so on ELSE statement_15; END IF; ... END; 46 Using a CASE Statement • Here is the same logic, but using a CASE statement. • It is much easier to read. v_numvar is written only once. DECLARE v_numvar NUMBER; BEGIN ... CASE v_numvar WHEN 5 THEN statement_1; statement_2; WHEN 10 THEN statement_3; WHEN 12 THEN statement_4; statement_5; WHEN 27 THEN statement_6; WHEN ... – and so on ELSE statement_15; END CASE; ... END; 47 CASE Statements: An Example A simple example to demonstrate the CASE logic. DECLARE v_num NUMBER := 15; v_txt VARCHAR2(50); BEGIN CASE v_num WHEN 20 THEN v_txt := 'number equals WHEN 17 THEN v_txt := 'number equals WHEN 15 THEN v_txt := 'number equals WHEN 13 THEN v_txt := 'number equals WHEN 10 THEN v_txt := 'number equals ELSE v_txt := 'some other number'; END CASE; DBMS_OUTPUT.PUT_LINE(v_txt); END; 20'; 17'; 15'; 13'; 10'; 48 Searched CASE Statements • You can use CASE statements to test for non-equality conditions such as <, >, >=, etc. • These are called searched CASE statements. • The syntax is virtually identical to an equivalent IF statement. DECLARE v_num NUMBER := 15; v_txt VARCHAR2(50); BEGIN CASE WHEN v_num > 20 THEN v_txt := 'greater than 20'; WHEN v_num > 15 THEN v_txt := 'greater than 15'; ELSE v_txt := 'less than 16'; END CASE; DBMS_OUTPUT.PUT_LINE(v_txt); END; 49 Using a CASE Expression • You want to assign a value to one variable that depends on the value in another variable. • Look at this IF statement. • Again, the coding is very repetitive. DECLARE VARCHAR2(15); v_out_var NUMBER; v_in_var BEGIN ... IF v_in_var = 1 THEN v_out_var := 'Low value'; ELSIF v_in_var = 50 THEN v_out_var := 'Middle value'; ELSIF v_in_var = 99 THEN v_out_var := 'High value'; ELSE v_out_var := 'Other value'; END IF; ... END; 50 Using a CASE Expression Here is the same logic, but using a CASE expression: DECLARE v_out_var VARCHAR2(15); v_in_var NUMBER; BEGIN ... v_out_var := CASE v_in_var WHEN 1 THEN 'Low value' WHEN 50 THEN 'Middle value' WHEN 99 THEN 'High value' ELSE 'Other value' END; ... END; 51 CASE Expression Syntax • A CASE expression selects one of a number of results and assigns it to a variable. • In the syntax, expressionN can be a literal value, such as 50, or an expression, such as (27+23) or (v_other_var*2). variable_name := CASE selector WHEN expression1 THEN result1 WHEN expression2 THEN result2 ... WHEN expressionN THEN resultN [ELSE resultN+1] END; 52 CASE Expression Example What would be the result of this code if v_grade was initialized as "C" instead of "A." DECLARE RESULT: v_grade CHAR(1) := 'A'; Grade: A v_appraisal VARCHAR2(20); Appraisal: Excellent BEGIN v_appraisal := Statement processed. CASE v_grade WHEN 'A' THEN 'Excellent' WHEN 'B' THEN 'Very Good' WHEN 'C' THEN 'Good' ELSE 'No such grade' END; DBMS_OUTPUT.PUT_LINE('Grade: ' || v_grade || ' Appraisal: ' || v_appraisal); END; 53 CASE Expression: A Second Example Determine what will be displayed when this block is executed: DECLARE v_out_var VARCHAR2(15); v_in_var NUMBER := 20; BEGIN v_out_var := CASE v_in_var WHEN 1 THEN 'Low value' WHEN v_in_var THEN 'Same value' WHEN 20 THEN 'Middle value' ELSE 'Other value' END; DBMS_OUTPUT.PUT_LINE(v_out_var); END; 54 Searched CASE Expression Syntax • PL/SQL also provides a searched CASE expression, which has the following form: variable_name := CASE WHEN search_condition1 THEN result1 WHEN search_condition2 THEN result2 ... WHEN search_conditionN THEN resultN [ELSE resultN+1] END; • A searched CASE expression has no selector. • Also, its WHEN clauses contain search conditions that yield a Boolean value, not expressions that can yield a value of any type. 55 Searched CASE Expressions: An Example Searched CASE expressions allow non-equality conditions, compound conditions, and different variables to be used in different WHEN clauses. DECLARE v_grade CHAR(1) := 'A'; v_appraisal VARCHAR2(20); BEGIN v_appraisal := CASE -- no selector here WHEN v_grade = 'A' THEN 'Excellent' WHEN v_grade IN ('B','C') THEN 'Good' ELSE 'No such grade' END; DBMS_OUTPUT.PUT_LINE ('Grade: '|| v_grade || ' Appraisal ' || v_appraisal); END; 56 How are CASE Expressions Different From CASE Statements? They are different because: • CASE expressions return a value into a variable. • CASE expressions end with END; • A CASE expression is a single PL/SQL statement. DECLARE v_grade CHAR(1) := 'A'; v_appraisal VARCHAR2(20); BEGIN v_appraisal := CASE WHEN v_grade = 'A' THEN 'Excellent' WHEN v_grade IN ('B','C') THEN 'Good' ELSE 'No such grade' END; DBMS_OUTPUT.PUT_LINE ('Grade: '|| v_grade || ' Appraisal ' || v_appraisal); END; 57 How are CASE Expressions Different From CASE Statements? • CASE statements evaluate conditions and perform actions. • A CASE statement can contain many PL/SQL statements. • CASE statements end with END CASE;. DECLARE v_grade CHAR(1) := 'A'; BEGIN CASE WHEN v_grade = 'A' THEN DBMS_OUTPUT.PUT_LINE ('Excellent'); WHEN v_grade IN ('B','C') THEN DBMS_OUTPUT.PUT_LINE ('Good'); ELSE DBMS_OUTPUT.PUT_LINE('No such grade'); END CASE; END; 58 Reference Oracle Academy PLSQL S4L1 Retrieving Data in PL/SQL PLSQL S4L2 Manipulating Data in PL/SQL Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Construct and identify loop statements. Iterative Control: LOOP Statements • Loops repeat a statement or a sequence of statements multiple times. • PL/SQL provides the following types of loops: – Basic loops that perform repetitive actions without overall conditions – FOR loops that perform iterative actions based on a counter – WHILE loops that perform repetitive actions based on a condition 64 Basic Loops • The simplest form of a LOOP statement is the basic loop, which encloses a sequence of statements between the keywords LOOP and END LOOP. • Use the basic loop when the statements inside the loop must execute at least once. 66 Basic Loops Exit • Each time the flow of execution reaches the END LOOP statement, control is passed to the corresponding LOOP statement that introduced it. • A basic loop allows the execution of its statements at least once, even if the EXIT condition is already met upon entering the loop. • Without the EXIT statement, the loop would never end (an infinite loop). BEGIN LOOP statements; EXIT [WHEN condition]; END LOOP; END; 67 Basic Loops Simple Example • In this example, no data is processed. • We simply display the loop counter each time we repeat the loop. DECLARE v_counter NUMBER(2) := 1; BEGIN LOOP DBMS_OUTPUT.PUT_LINE('Loop execution #' || v_counter); v_counter := v_counter + 1; EXIT WHEN v_counter > 5; END LOOP; END; 68 Basic Loops More Complex Example In this example, three new location IDs for Montreal, Canada, are inserted in the LOCATIONS table. DECLARE v_loc_id locations.location_id%TYPE; v_counter NUMBER(2) := 1; BEGIN SELECT MAX(location_id) INTO v_loc_id FROM locations WHERE country_id = 2; LOOP INSERT INTO locations(location_id, city, country_id) VALUES((v_loc_id + v_counter), 'Montreal', 2); v_counter := v_counter + 1; EXIT WHEN v_counter > 3; END LOOP; END; 69 Basic Loops EXIT Statement • You can use the EXIT statement to terminate a loop and pass control to the next statement after the END LOOP statement. • You can issue EXIT as an action within an IF statement. DECLARE v_counter NUMBER := 1; BEGIN LOOP DBMS_OUTPUT.PUT_LINE('Counter is ' || v_counter); v_counter := v_counter + 1; IF v_counter > 10 THEN EXIT; END IF; END LOOP; END; 70 Basic Loop EXIT Statement Rules Rules: • The EXIT statement must be placed inside a loop. • If the EXIT condition is placed at the top of the loop (before any of the other executable statements) and that condition is initially true, then the loop exits and the other statements in the loop never execute. • A basic loop can contain multiple EXIT statements. 71 Basic Loop EXIT WHEN Statement • Although the IF…THEN EXIT works to end a loop, the correct way to end a basic loop is with the EXIT WHEN statement. • If the WHEN clause evaluates to TRUE, the loop ends and control passes to the next statement following END LOOP. DECLARE v_counter NUMBER := 1; BEGIN LOOP DBMS_OUTPUT.PUT_LINE('Counter is ' || v_counter); v_counter := v_counter + 1; EXIT WHEN v_counter > 10; END LOOP; END; 72 WHILE Loops • You can use the WHILE loop to repeat a sequence of statements until the controlling condition is no longer TRUE. • The condition is evaluated at the start of each iteration. • The loop terminates when the condition is FALSE or NULL. • If the condition is FALSE or NULL at the initial execution of the loop, then no iterations are performed. WHILE condition LOOP statement1; statement2; . . . END LOOP; 74 WHILE Loops • In the syntax: WHILE condition LOOP statement1; statement2; . . . END LOOP; • Condition is a Boolean variable or expression (TRUE, FALSE, or NULL) • Statement can be one or more PL/SQL or SQL statements PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 75 WHILE • In the syntax: WHILE condition LOOP statement1; statement2; . . . END LOOP; • If the variables involved in the conditions do not change during the body of the loop, then the condition remains TRUE and the loop does not terminate. • Note: If the condition yields NULL, then the loop is bypassed and control passes to the statement that follows the loop. PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 76 WHILE Loops • In this example, three new location IDs for Montreal, Canada, are inserted in the LOCATIONS table. • The counter is explicitly declared in this example. DECLARE v_loc_id locations.location_id%TYPE; v_counter NUMBER := 1; BEGIN SELECT MAX(location_id) INTO v_loc_id FROM locations WHERE country_id = 2; WHILE v_counter <= 3 LOOP INSERT INTO locations(location_id, city, country_id) VALUES((v_loc_id + v_counter), 'Montreal', 2); v_counter := v_counter + 1; END LOOP; END; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 77 WHILE Loops • With each iteration through the WHILE loop, a counter (v_counter) is incremented. • If the number of iterations is less than or equal to the number 3, then the code within the loop is executed and a row is inserted into the locations table. DECLARE v_loc_id locations.location_id%TYPE; v_counter NUMBER := 1; BEGIN SELECT MAX(location_id) INTO v_loc_id FROM locations WHERE country_id = 2; WHILE v_counter <= 3 LOOP INSERT INTO locations(location_id, city, country_id) VALUES((v_loc_id + v_counter), 'Montreal', 2); v_counter := v_counter + 1; END LOOP; END; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 78 WHILE Loops After the counter exceeds the number of new locations for this city and country, the condition that controls the loop evaluates to FALSE and the loop is terminated. DECLARE v_loc_id locations.location_id%TYPE; v_counter NUMBER := 1; BEGIN SELECT MAX(location_id) INTO v_loc_id FROM locations WHERE country_id = 2; WHILE v_counter <= 3 LOOP INSERT INTO locations(location_id, city, country_id) VALUES((v_loc_id + v_counter), 'Montreal', 2); v_counter := v_counter + 1; END LOOP; END; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 79 FOR Loops Described • FOR loops have the same general structure as the basic loop. • In addition, they have a control statement before the LOOP keyword to set the number of iterations that PL/SQL performs. FOR counter IN [REVERSE] lower_bound..upper_bound LOOP statement1; statement2; . . . END LOOP; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 80 FOR Loop Rules FOR loop rules: • Use a FOR loop to shortcut the test for the number of iterations. • Do not declare the counter; it is declared implicitly. • lower_bound .. upper_bound is the required syntax. FOR counter IN [REVERSE] lower_bound..upper_bound LOOP statement1; statement2; . . . END LOOP; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 81 FOR Loops Syntax • Counter is an implicitly declared integer whose value automatically increases or decreases (decreases if the REVERSE keyword is used) by 1 on each iteration of the loop until the upper or lower bound is reached. • REVERSE causes the counter to decrement with each iteration from the upper bound to the lower bound. • (Note that the lower bound is referenced first.) FOR counter IN [REVERSE] lower_bound..upper_bound LOOP statement1; statement2; . . . END LOOP; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 82 FOR Loops Syntax • lower_bound specifies the lower bound for the range of counter values. • upper_bound specifies the upper bound for the range of counter values. FOR counter IN [REVERSE] lower_bound..upper_bound LOOP statement1; statement2; . . . END LOOP; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 83 FOR Loop Example • You have already learned how to insert three new locations for the country code CA and the city Montreal by using the simple LOOP and the WHILE loop. • This slide shows you how to achieve the same by using the FOR loop. DECLARE v_loc_id locations.location_id%TYPE; BEGIN SELECT MAX(location_id) INTO v_loc_id FROM locations WHERE country_id = 2; FOR i IN 1..3 LOOP INSERT INTO locations(location_id, city, country_id) VALUES((v_loc_id + i), 'Montreal', 2); END LOOP; END; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 84 FOR Loop Guidelines FOR loops are a common structure of programming languages. • A FOR loop is used within the code when the beginning and ending value of the loop is known. • Reference the counter only within the loop; its scope does not extend outside the loop. • Do not reference the counter as the target of an assignment. • Neither loop bound (lower or upper) should be NULL. PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 85 FOR Loop Expression Example • While writing a FOR loop, the lower and upper bounds of a LOOP statement do not need to be numeric literals. • They can be expressions that convert to numeric values. DECLARE v_lower NUMBER := 1; v_upper NUMBER := 100; BEGIN FOR i IN v_lower..v_upper LOOP ... END LOOP; END; PLSQL S4L4 Iterative Control: WHILE and FOR Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 86 Nested Loop Example • In PL/SQL, you can nest loops to multiple levels. • You can nest FOR, WHILE, and basic loops within one another. BEGIN FOR v_outerloop IN 1..3 LOOP FOR v_innerloop IN REVERSE 1..5 LOOP DBMS_OUTPUT.PUT_LINE('Outer loop is: ' || v_outerloop || ' and inner loop is: ' || v_innerloop); END LOOP; END LOOP; END; PLSQL S4L5 Iterative Control: Nested Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 88 Nested Loops • This example contains EXIT conditions in nested basic loops. • What if you want to exit from the outer loop at step A? DECLARE CHAR(3) := 'NO'; v_outer_done CHAR(3) := 'NO'; v_inner_done BEGIN LOOP -- outer loop ... -- inner loop LOOP ... ... -- step A EXIT WHEN v_inner_done = 'YES'; ... END LOOP; ... EXIT WHEN v_outer_done = 'YES'; ... END LOOP; END; PLSQL S4L5 Iterative Control: Nested Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 89 Loop Labels Loop labels are required in this example in order to exit an outer loop from within an inner loop DECLARE ... BEGIN <<outer_loop>> LOOP -- outer loop ... <<inner_loop>> LOOP -- inner loop EXIT outer_loop WHEN ... -- exits both loops EXIT WHEN v_inner_done = 'YES'; ... END LOOP; ... EXIT WHEN v_outer_done = 'YES'; ... END LOOP; END; PLSQL S4L5 Iterative Control: Nested Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 90 Loop Labels • Loop label names follow the same rules as other identifiers. • A label is placed before a statement, either on the same line or on a separate line. • In FOR or WHILE loops, place the label before FOR or WHILE within label delimiters (<<label>>). • If the loop is labeled, the label name can optionally be included after the END LOOP statement for clarity. PLSQL S4L5 Iterative Control: Nested Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 91 Loop Labels Label basic loops by placing the label before the word LOOP within label delimiters (<<label>>). DECLARE PLS_INTEGER := 0; v_outerloop PLS_INTEGER := 5; v_innerloop BEGIN <<outer_loop>> LOOP v_outerloop := v_outerloop + 1; v_innerloop := 5; EXIT WHEN v_outerloop > 3; <<inner_loop>> LOOP DBMS_OUTPUT.PUT_LINE('Outer loop is: ' || v_outerloop || ' and inner loop is: ' || v_innerloop); v_innerloop := v_innerloop - 1; EXIT WHEN v_innerloop = 0; END LOOP inner_loop; END LOOP outer_loop; END; PLSQL S4L5 Iterative Control: Nested Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 92 Nested Loops and Labels • In this example, there are two loops. • The outer loop is identified by the label <<outer_loop>>, and the inner loop is identified by the label <<inner_loop>>. ...BEGIN <<outer_loop>> LOOP v_counter := v_counter + 1; EXIT WHEN v_counter > 10; <<inner_loop>> LOOP ... EXIT Outer_loop WHEN v_total_done = 'YES'; -- Leave both loops EXIT WHEN v_inner_done = 'YES'; ... -- Leave inner loop only ... END LOOP inner_loop; END LOOP outer_loop; END; PLSQL S4L5 Iterative Control: Nested Loops Copyright © 2016, Oracle and/or its affiliates. All rights reserved. 93 Reference Oracle Academy PLSQL S4L3 Iterative Control: Basic Loops PLSQL S4L4 Iterative Control: While and For Loops PLSQL S4L5 Iterative Control: Nested Loops Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Create a record with the %ROWTYPE attribute. • Create user-defined PL/SQL records. PL/SQL Records • A PL/SQL record is a composite data type consisting of a group of related data items stored as fields, each with its own name and data type. • You can refer to the whole record by its name and/or to individual fields by their names. • Typical syntax for defining a record, shown below, is similar to what we used for cursors, we just replace the cursor name with the table name. record_name table_name%ROWTYPE; 5 The Problem • The EMPLOYEES table contains eleven columns: EMPLOYEE_ID, FIRST_NAME,...., MANAGER_ID, DEPARTMENT_ID. • You need to code a single-row SELECT * FROM EMPLOYEES in your PL/SQL subprogram. • Because you are selecting only a single row, you do not need to declare and use a cursor. • How many scalar variables must you DECLARE to hold the column values? 6 The Problem • That is a lot of coding, and some tables will have even more columns. • Plus, what do you do if a new column is added to the table? • Or an existing column is dropped? DECLARE v_employee_id employees.employee_id%TYPE; v_first_name employees.first_name%TYPE; v_last_name employees.last_name%TYPE; v_email employees.email%TYPE; ... FIVE MORE SCALAR VARIABLES REQUIRED TO MATCH THE TABLE v_manager_id employees.manager_id%TYPE; v_department_id employees.department_id%TYPE; BEGIN SELECT employee_id, first_name, ... EIGHT MORE HERE, department_id INTO v_employee_id, v_first_name, ... AND HERE, v_department_id FROM employees WHERE employee_id = 100; END; 7 The Problem • Look at the code again. Wouldn’t it be easier to declare one variable instead of eleven? • As it did with cursors, %ROWTYPE allows us to declare a variable as a record based on a particular table's structure. • Each field or component within the record will have its own name and data type based on the table's structure. • You can refer to the whole record by its name individual fields by their names. and to 8 The Solution - Use a PL/SQL Record • Use %ROWTYPE to declare a variable as a record based on the structure of the employees table. • Less code to write and nothing to change if columns are added or dropped. DECLARE v_emp_record employees%ROWTYPE; BEGIN SELECT * INTO v_emp_record FROM employees WHERE employee_id = 100; DBMS_OUTPUT.PUT_LINE('Email for ' || v_emp_record.first_name || ' ' || v_emp_record.last_name || ' is ' || v_emp_record.email || '@oracle.com.'); END; 9 A Record Based on Another Record You can use %ROWTYPE to declare a record based on another record: DECLARE v_emp_record employees%ROWTYPE; v_emp_copy_record v_emp_record%ROWTYPE; BEGIN SELECT * INTO v_emp_record FROM employees WHERE employee_id = 100; v_emp_copy_record := v_emp_record; v_emp_copy_record.salary := v_emp_record.salary * 1.2; DBMS_OUTPUT.PUT_LINE(v_emp_record.first_name || ' ' || v_emp_record.last_name || ': Old Salary - ' || v_emp_record.salary || ', Proposed New Salary - ' || v_emp_copy_record.salary || '.'); END; 10 Defining Your Own Records • What if you need data from a join of multiple tables? • You can declare your own record structures containing any fields you like. • PL/SQL records: – Must contain one or more components/fields of any scalar or composite type – Are not the same as rows in a database table – Can be assigned initial values and can be defined as NOT NULL – Can be components of other records (nested records). 11 Syntax for User-Defined Records • Start with the TYPE keyword to define your record structure. • It must include at least one field and the fields may be defined using scalar data types such as DATE, VARCHAR2, or NUMBER, or using attributes such as %TYPE and %ROWTYPE. • After declaring the type, use the type_name to declare a variable of that type. TYPE type_name IS RECORD (field_declaration[,field_declaration]...); identifier type_name; 12 User-Defined Records: Example 1 • First, declare/define the type and a variable of that type. • Then use the variable and its components. DECLARE TYPE person_dept IS RECORD (first_name employees.first_name%TYPE, last_name employees.last_name%TYPE, department_name departments.department_name%TYPE); v_person_dept_rec person_dept; BEGIN SELECT e.first_name, e.last_name, d.department_name INTO v_person_dept_rec FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE employee_id = 200; DBMS_OUTPUT.PUT_LINE(v_person_dept_rec.first_name || ' ' || v_person_dept_rec.last_name || ' is in the ' || v_person_dept_rec.department_name || ' department.'); END; 13 User-Defined Records: Example 2 • Here we have two custom data types, one nested within the other. • How many fields can be addressed in v_emp_dept_rec? DECLARE TYPE dept_info_type IS RECORD (department_id departments.department_id%TYPE, department_name departments.department_name%TYPE) TYPE emp_dept_type IS RECORD employees.first_name%TYPE, (first_name employees.last_name%TYPE, last_name dept_info dept_info_type); v_emp_dept_rec BEGIN ... END; emp_dept_type; 14 Declaring and Using Types and Records • Types and records are composite structures that can be declared anywhere that scalar variables can be declared in anonymous blocks, procedures, functions, package specifications (global), package bodies (local), triggers, and so on. • Their scope and visibility follow the same rules as for scalar variables. • For example, you can declare a type (and a record based on the type) in an outer block and reference them within an inner block. 15 Visibility and Scope of Types and Records The type and the record declared in the outer block are visible within the outer block and the inner block. DECLARE -- outer block TYPE employee_type IS RECORD (first_name employees.first_name%TYPE := 'Amy'); employee_type; v_emp_rec_outer BEGIN DBMS_OUTPUT.PUT_LINE(v_emp_rec_outer.first_name); DECLARE -- inner block v_emp_rec_inner employee_type; BEGIN v_emp_rec_outer.first_name := 'Clara'; DBMS_OUTPUT.PUT_LINE(v_emp_rec_outer.first_name || ' and ' || v_emp_rec_inner.first_name); END; DBMS_OUTPUT.PUT_LINE(v_emp_rec_outer.first_name); END; 16 Reference Oracle Academy PLSQL S6L1 User-Defined Records Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Distinguish between implicit and explicit cursors. • Discuss the reasons for using explicit cursors. • Declare and control explicit cursors. • Use simple loops and cursor FOR loops to fetch data. Context Areas and Cursors • The Oracle server allocates a private memory area called a context area to store the data processed by a SQL statement. • Every context area (and therefore every SQL statement) has a cursor associated with it. Cursor Context Area 22 Context Areas and Cursors • You can think of a cursor either as a label for the context area, or as a pointer to the context area. • In fact, a cursor is both of these items. Cursor Context Area 23 Implicit and Explicit Cursors There are two types of cursors: • Implicit cursors: Defined automatically by Oracle for all SQL DML statements (INSERT, UPDATE, DELETE, and MERGE), and for SELECT statements that return only one row. • Explicit cursors: Declared by the programmer for queries that return more than one row. – You can use explicit cursors to name a context area and access its stored data. 24 Limitations of Implicit Cursors • Programmers must think about the data that is possible as well as the data that actually exists now. • If there ever is more than one row in the EMPLOYEES table, the SELECT statement below (without a WHERE clause) will cause an error. DECLARE v_salary employees.salary%TYPE; BEGIN SELECT salary INTO v_salary FROM employees; DBMS_OUTPUT.PUT_LINE(' Salary is : '||v_salary); END; ORA-01422: exact fetch returns more than requested number of rows 25 Explicit Cursors • With an explicit cursor, you can retrieve multiple rows from a database table, have a pointer to each row that is retrieved, and work on the rows one at a time. • Reasons to use an explicit cursor: – It is the only way in PL/SQL to retrieve more than one row from a table. – Each row is fetched by a separate program statement, giving the programmer more control over the processing of the rows. 26 Example of an Explicit Cursor The following example uses an explicit cursor to display each row from the departments table. DECLARE CURSOR cur_depts IS SELECT department_id, department_name FROM departments; v_department_id departments.department_id%TYPE; departments.department_name%TYPE; v_department_name BEGIN OPEN cur_depts; LOOP FETCH cur_depts INTO v_department_id, v_department_name; EXIT WHEN cur_depts%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_department_id||' '||v_department_name); END LOOP; CLOSE cur_depts; END; 27 Explicit Cursor Operations • The set of rows returned by a multiple-row query is called the active set, and is stored in the context area. • Its size is the number of rows that meet your query criteria. Table Explicit Cursor 100 King AD_PRES 101 Kochhar AD_VP 102 De Haan AD_VP 139 Seo ST_CLERK Active set 28 Explicit Cursor Operations • Think of the context area (named by the cursor) as a box, and the active set as the contents of the box. • To get at the data, you must OPEN the box and FETCH each row from the box one at a time. • When finished, you must CLOSE the box. Explicit Cursor Table 100 King AD_PRES 101 Kochhar AD_VP 102 De Haan AD_VP 139 Seo ST_CLERK Active set 29 Controlling Explicit Cursors 1 Open the cursor. Cursor pointer 2 Fetch each row, one at a time. 3 Close the cursor. Cursor pointer Cursor pointer 30 Steps for Using Explicit Cursors You first DECLARE a cursor, and then you use the OPEN, FETCH, and CLOSE statements to control a cursor. DECLARE OPEN FETCH EXIT Define the cursor. Fill the cursor active set with data. Retrieve the current row into variables. Test for remaining rows. CLOSE Release the active set. Return to FETCH if additional row found. 31 Steps for Using Explicit Cursors Now that you have a conceptual understanding of cursors, review the steps to use them: • DECLARE the cursor in the declarative section by naming it and defining the SQL SELECT statement to be associated with it. • OPEN the cursor. – This will populate the cursor's active set with the results of the SELECT statement in the cursor's definition. – The OPEN statement also positions the cursor pointer at the first row. 32 Steps for Using Explicit Cursors Now that you have a conceptual understanding of cursors, review the steps to use them: • FETCH each row from the active set and load the data into variables. – After each FETCH, the EXIT WHEN checks to see if the FETCH reached the end of the active set resulting in a data NOTFOUND condition. – If the end of the active set was reached, the LOOP is exited. • CLOSE the cursor. – The CLOSE statement releases the active set of rows. – It is now possible to reopen the cursor to establish a fresh active set using a new OPEN statement. 33 Declaring the Cursor When declaring the cursor: • Do not include the INTO clause in the cursor declaration because it appears later in the FETCH statement. • If processing rows in a specific sequence is required, then use the ORDER BY clause in the query. • The cursor can be any valid SELECT statement, including joins, subqueries, and so on. • If a cursor declaration references any PL/SQL variables, these variables must be declared before declaring the cursor. 34 Syntax for Declaring the Cursor • The active set of a cursor is determined by the SELECT statement in the cursor declaration. • Syntax: CURSOR cursor_name IS select_statement; • In the syntax: – cursor_name – select_statement Is a PL/SQL identifier Is a SELECT statement without an INTO clause 35 Declaring the Cursor Example 1 The cur_emps cursor is declared to retrieve the employee_id and last_name columns of the employees working in the department with a department_id of 30. DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees WHERE department_id = 30; ... 36 Declaring the Cursor Example 2 • The cur_depts cursor is declared to retrieve all the details for the departments with the location_id 1700. • You want to fetch and process these rows in ascending sequence by department_name. DECLARE CURSOR cur_depts IS SELECT * FROM departments WHERE location_id = 1700 ORDER BY department_name; ... 37 Declaring the Cursor Example 3 • A SELECT statement in a cursor declaration can include joins, group functions, and subqueries. • This example retrieves each department that has at least two employees, giving the department name and number of employees. DECLARE CURSOR cur_depts_emps IS SELECT department_name, COUNT(*) AS how_many FROM departments d, employees e WHERE d.department_id = e.department_id GROUP BY d.department_name HAVING COUNT(*) > 1; ... 38 Opening the Cursor • The OPEN statement executes the query associated with the cursor, identifies the active set, and positions the cursor pointer to the first row. • The OPEN statement is included in the executable section of the PL/SQL block. DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees WHERE department_id = 30; ... BEGIN OPEN cur_emps; ... 39 Opening the Cursor The OPEN statement performs the following operations: • Allocates memory for a context area (creates the box to hold the data) • Executes the SELECT statement in the cursor declaration, returning the results into the active set (fills the box with the data) • Positions the pointer to the first row in the active set 40 Fetching Data from the Cursor • The FETCH statement retrieves the rows from the cursor one at a time. • After each successful fetch, the cursor advances to the next row in the active set. DECLARE CURSOR emp_cursor IS SELECT employee_id, last_name FROM employees WHERE department_id =10; v_empno employees.employee_id%TYPE; v_lname employees.last_name%TYPE; BEGIN OPEN emp_cursor; FETCH emp_cursor INTO v_empno, v_lname; DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname); ... END; 41 Fetching Data from the Cursor • Two variables, v_empno and v_lname, were declared to hold the values fetched from the cursor. DECLARE CURSOR emp_cursor IS SELECT employee_id, last_name FROM employees WHERE department_id =10; v_empno employees.employee_id%TYPE; v_lname employees.last_name%TYPE; BEGIN OPEN emp_cursor; FETCH emp_cursor INTO v_empno, v_lname; DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname); ... END; 42 Fetching Data from the Cursor • The previous code successfully fetched the values from the first row in the cursor into the variables. • If there are other employees in department 50, you have to use a loop as shown below to access and process each row. DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees WHERE department_id =50; v_empno employees.employee_id%TYPE; v_lname employees.last_name%TYPE; BEGIN OPEN cur_emps; LOOP FETCH cur_emps INTO v_empno, v_lname; EXIT WHEN cur_emps%NOTFOUND; DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname); END LOOP; … END; 43 Guidelines for Fetching Data From the Cursor Follow these guidelines when fetching data from the cursor: • Include the same number of variables in the INTO clause of the FETCH statement as columns in the SELECT statement, and be sure that the data types are compatible. • Match each variable to correspond to the columns position in the cursor definition. • Use %TYPE to ensure data types are compatible between variable and table. 44 Guidelines for Fetching Data From the Cursor Follow these guidelines when fetching data from the cursor: • Test to see whether the cursor contains rows. • If a fetch acquires no values, then there are no rows to process (or left to process) in the active set and no error is recorded. • You can use the %NOTFOUND cursor attribute to test for the exit condition. 45 Fetching Data From the Cursor Example 1 What is wrong with this example? DECLARE CURSOR cur_emps IS SELECT employee_id, last_name, salary FROM employees WHERE department_id =30; v_empno employees.employee_id%TYPE; v_lname employees.last_name%TYPE; v_sal employees.salary%TYPE; BEGIN OPEN cur_emps; LOOP FETCH cur_emps INTO v_empno, v_lname; EXIT WHEN cur_emps%NOTFOUND; DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname); END LOOP; … END; 46 Fetching Data From the Cursor Example 2 • There is only one employee in department 10. • What happens when this example is executed? DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees WHERE department_id =10; v_empno employees.employee_id%TYPE; v_lname employees.last_name%TYPE; BEGIN OPEN cur_emps; LOOP FETCH cur_emps INTO v_empno, v_lname; DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || v_lname); END LOOP; … END; 47 Closing the Cursor • The CLOSE statement disables the cursor, releases the context area, and undefines the active set. • You should close the cursor after completing the processing of the FETCH statement. ... LOOP FETCH cur_emps INTO v_empno, v_lname; EXIT WHEN cur_emps%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || v_lname); END LOOP; CLOSE cur_emps; END; 48 Closing the Cursor • You can reopen the cursor later if required. • Think of CLOSE as closing and emptying the box, so you can no longer FETCH its contents. ... LOOP FETCH cur_emps INTO v_empno, v_lname; EXIT WHEN cur_emps%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || v_lname); END LOOP; CLOSE cur_emps; END; 49 Guidelines for Closing the Cursor Follow these guidelines when closing the cursor: • A cursor can be reopened only if it is closed. • If you attempt to fetch data from a cursor after it has been closed, then an INVALID_CURSOR exception is raised. • If you later reopen the cursor, the associated SELECT statement is re-executed to re-populate the context area with the most recent data from the database. 50 Putting It All Together Now, when looking at an explicit cursor, you should be able to identify the cursor-related keywords and explain what each statement is doing. DECLARE CURSOR cur_depts IS SELECT department_id, department_name FROM departments v_department_id departments.department_id%TYPE; v_department_name departments.department_name%TYPE; BEGIN OPEN cur_depts; LOOP FETCH cur_depts INTO v_department_id, v_department_name; EXIT WHEN cur_depts%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_department_id||' '||v_department_name); END LOOP; CLOSE cur_depts; END; 51 Explicit Cursor Attributes • As with implicit cursors, there are several attributes for obtaining status information about an explicit cursor. • When appended to the cursor variable name, these attributes return useful information about the execution of a cursor manipulation statement. Attribute Type Description %ISOPEN Boolean Evaluates to TRUE if the cursor is open. %NOTFOUND Boolean Evaluates to TRUE if the most recent fetch did not return a row. %FOUND Boolean Evaluates to TRUE if the most recent fetch returned a row; opposite of %NOTFOUND. %ROWCOUNT Number Evaluates to the total number of rows FETCHed so far. 53 %ISOPEN Attribute • You can fetch rows only when the cursor is open. • Use the %ISOPEN cursor attribute before performing a fetch to test whether the cursor is open. • %ISOPEN returns the status of the cursor: TRUE if open and FALSE if not. • Example: IF NOT cur_emps%ISOPEN THEN OPEN cur_emps; END IF; LOOP FETCH cur_emps... 54 %ROWCOUNT and %NOTFOUND Attributes • Usually the %ROWCOUNT and %NOTFOUND attributes are used in a loop to determine when to exit the loop. • Use the %ROWCOUNT cursor attribute for the following: – To process an exact number of rows – To count the number of rows fetched so far in a loop and/or determine when to exit the loop 55 %ROWCOUNT and %NOTFOUND Attributes Use the %NOTFOUND cursor attribute for the following: • To determine whether the query found any rows matching your criteria • To determine when to exit the loop 56 Example of %ROWCOUNT and %NOTFOUND This example shows how you can use %ROWCOUNT and %NOTFOUND attributes for exit conditions in a loop. DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees; v_emp_record cur_emps%ROWTYPE; BEGIN OPEN cur_emps; LOOP FETCH cur_emps INTO v_emp_record; EXIT WHEN cur_emps%ROWCOUNT > 10 OR cur_emps%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_emp_record.employee_id || ' ' || v_emp_record.last_name); END LOOP; CLOSE cur_emps; END; 57 Explicit Cursor Attributes in SQL Statements • You cannot use an explicit cursor attribute directly in an SQL statement. • The following code returns an error: DECLARE CURSOR cur_emps IS SELECT employee_id, salary FROM employees ORDER BY SALARY DESC; v_emp_record cur_emps%ROWTYPE; v_count NUMBER; BEGIN OPEN cur_emps; LOOP FETCH cur_emps INTO v_emp_record; EXIT WHEN cur_emps%NOTFOUND; INSERT INTO top_paid_emps (employee_id, rank, salary) VALUES (v_emp_record.employee_id, cur_emps%ROWCOUNT, v_emp_record.salary); ... 58 Explicit Cursor Attributes in SQL Statements • To avoid the error on the previous slide,we would copy the cursor attribute value to a variable, then use the variable in the SQL statement: Cursor FOR Loops • A cursor FOR loop processes rows in an explicit cursor. • It is a shortcut because the cursor is opened, a row is fetched once for each iteration in the loop, the loop exits when the last row is processed, and the cursor is closed automatically. • The loop itself is terminated automatically at the end of the iteration when the last row has been fetched. • Syntax: FOR record_name IN cursor_name LOOP statement1; statement2; . . . END LOOP; 61 Cursor FOR Loops In the syntax: • record_name • cursor_name Is the name of the implicitly declared record (as cursor_name%ROWTYPE) Is a PL/SQL identifier for a previously declared cursor FOR record_name IN cursor_name LOOP statement1; statement2; . . . END LOOP; 62 Cursor FOR Loops • You can simplify your coding by using a cursor FOR loop instead of the OPEN, FETCH, and CLOSE statements. • A cursor FOR loop implicitly declares its loop counter as a record that represents a row FETCHed from the database. • A cursor FOR loop: –OPENs a cursor. – Repeatedly FETCHes rows of values from the active set into fields in the record. – CLOSEs the cursor when all rows have been processed. Cursor FOR Loops • Note: v_emp_record is the record that is implicitly declared. • You can access the fetched data with this implicit record as shown below. 64 Cursor FOR Loops • No variables are declared to hold the fetched data and no INTO clause is required. • OPEN and CLOSE statements are not required, they happen automatically in this syntax. Cursor FOR Loops • There is no need to declare the variable v_emp_rec in the declarative section. The syntax "FOR v_emp_rec IN …" implicitly defines v_emp_rec. • The scope of the implicit record is restricted to the loop, so you cannot reference the fetched data outside the loop. Cursor FOR Loops • Compare the cursor FOR loop (on the left) with the cursor code you learned in the previous lesson. • The two forms of the code are logically identical to each other and produce exactly the same results. DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees WHERE department_id = 50; BEGIN FOR v_emp_rec IN cur_emps LOOP DBMS_OUTPUT.PUT_LINE(…); END LOOP; END; DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees WHERE department_id = 50; v_emp_rec cur_emps%ROWTYPE; BEGIN OPEN cur_emps; LOOP FETCH cur_emps INTO v_emp_rec; EXIT WHEN cur_emps%NOTFOUND; DBMS_OUTPUT.PUT_LINE(…); END LOOP; CLOSE cur_emps; END; 67 Guidelines for Cursor FOR Loops Guidelines: • Do not declare the record that controls the loop because it is declared implicitly. • The scope of the implicit record is restricted to the loop, so you cannot reference the record outside the loop. • You can access fetched data using record_name.column_name. 68 Testing Cursor Attributes • You can still test cursor attributes, such as %ROWCOUNT. • This example exits from the loop after five rows have been fetched and processed. • The cursor is still closed automatically. DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees; BEGIN FOR v_emp_record IN cur_emps LOOP EXIT WHEN cur_emps%ROWCOUNT > 5; DBMS_OUTPUT.PUT_LINE(v_emp_record.employee_id || ' ' || v_emp_record.last_name); END LOOP; END; 69 Cursor FOR Loops Using Subqueries • You can go one step further. You don’t have to declare the cursor at all! • Instead, you can specify the SELECT on which the cursor is based directly in the FOR loop. • The advantage of this is the cursor definition is contained in a single FOR … statement. • In complex code with lots of cursors, this simplification makes code maintenance easier and quicker. • The downside is you can't reference cursor attributes. 70 Cursor FOR Loops Using Subqueries: Example The SELECT clause in the FOR statement is technically a subquery, so you must enclose it in parentheses. BEGIN FOR v_emp_record IN (SELECT employee_id, last_name FROM employees WHERE department_id = 50) LOOP DBMS_OUTPUT.PUT_LINE(v_emp_record.employee_id || ' ' || v_emp_record.last_name); END LOOP; END; 71 Cursor FOR Loops Using Subqueries • Again, compare these two forms of code. • They are logically identical, but which one would you rather write – especially if you hate typing! BEGIN FOR v_dept_rec IN (SELECT * FROM departments) LOOP DBMS_OUTPUT.PUT_LINE(…); END LOOP; END; DECLARE CURSOR cur_depts IS SELECT * FROM departments; v_dept_rec cur_depts%ROWTYPE; BEGIN OPEN cur_depts; LOOP FETCH cur_depts INTO v_dept_rec; EXIT WHEN cur_depts%NOTFOUND; DBMS_OUTPUT.PUT_LINE(…); END LOOP; CLOSE cur_depts; END; 72 Reference Oracle Academy PLSQL S5L1 Introduction to Explicit Cursors PLSQL S5L2 Using Explicit Cursor Attributes PLSQL S5L3 Cursor FOR Loops Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Declare and use cursors with parameters. • Lock rows with the FOR UPDATE clause. Cursors with Parameters • A parameter is a variable whose name is used in a cursor declaration. • When the cursor is opened, the parameter value is passed to the Oracle server, which uses it to decide which rows to retrieve into the active set of the cursor. 78 Cursors with Parameters • This means that you can open and close an explicit cursor several times in a block, or in different executions of the same block, returning a different active set on each occasion. • Consider an example where you pass a location_id to a cursor and it returns the names of the departments at that location. 79 Cursors with Parameters: Example DECLARE CURSOR cur_country (p_region_id NUMBER) IS SELECT country_id, country_name FROM countries WHERE region_id = p_region_id; v_country_record cur_country%ROWTYPE; BEGIN Change to whichever OPEN cur_country (5); region is required. LOOP . FETCH cur_country INTO v_country_record; EXIT WHEN cur_country%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_country_record.country_id || ' ' || v_country_record.country_name); END LOOP; CLOSE cur_country; END; 80 Defining Cursors with Parameters Syntax • Each parameter named in the cursor declaration must have a corresponding value in the OPEN statement. • Parameter data types are the same as those for scalar variables, but you do not give them sizes. • The parameter names are used in the WHERE clause of the cursor SELECT statement. CURSOR cursor_name [(parameter_name datatype, ...)] IS select_statement; 81 Defining Cursors with Parameters Syntax In the syntax: • cursor_name Is a PL/SQL identifier for the declared cursor • parameter_name Is the name of a parameter • datatype Is the scalar data type of the parameter • select_statement Is a SELECT statement without the INTO clause CURSOR cursor_name [(parameter_name datatype, ...)] IS select_statement; 82 Opening Cursors with Parameters The following is the syntax for opening a cursor with parameters: OPEN cursor_name(parameter_value1, parameter_value2, ...); 83 Cursors with Parameters • You pass parameter values to a cursor when the cursor is opened. • Therefore you can open a single explicit cursor several times and fetch a different active set each time. • In the following example, a cursor is opened several times. DECLARE CURSOR cur_countries (p_region_id NUMBER) IS SELECT country_id, country_name FROM countries WHERE region_id = p_region_id; v_country_record c_countries%ROWTYPE; BEGIN OPEN cur_countries (5); Open the cursor again and ... CLOSE cur_countries; return a different active set. OPEN cur_countries (145); ... 84 Another Example of a Cursor with a Parameter DECLARE v_deptid employees.department_id%TYPE; CURSOR cur_emps (p_deptid NUMBER) IS SELECT employee_id, salary FROM employees WHERE department_id = p_deptid; v_emp_rec cur_emps%ROWTYPE; BEGIN SELECT MAX(department_id) INTO v_deptid FROM employees; OPEN cur_emps(v_deptid); LOOP FETCH cur_emps INTO v_emp_rec; EXIT WHEN cur_emps%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_emp_rec.employee_id || ' || v_emp_rec.salary); END LOOP; CLOSE cur_emps; END; ' 85 Cursor FOR Loops wıth a Parameter We can use a cursor FOR loop if needed: DECLARE CURSOR cur_emps (p_deptno NUMBER) IS SELECT employee_id, last_name FROM employees WHERE department_id = p_deptno; BEGIN FOR v_emp_record IN cur_emps(10) LOOP ... END LOOP; END; 86 Cursors with Multiple Parameters: Example 1 In the following example, a cursor is declared and is called with two parameters: DECLARE CURSOR cur_countries (p_region_id NUMBER, p_population NUMBER) IS SELECT country_id, country_name, population FROM countries WHERE region_id = p_region_id OR population > p_population; BEGIN FOR v_country_record IN cur_countries(145,10000000) LOOP DBMS_OUTPUT.PUT_LINE(v_country_record.country_id ||' ' || v_country_record. country_name||' ' || v_country_record.population); END LOOP; END; 87 Cursors with Multiple Parameters: Example 2 This cursor fetches all IT Programmers who earn more than $10,000.00 DECLARE CURSOR cur_emps (p_job VARCHAR2, p_salary NUMBER) IS SELECT employee_id, last_name FROM employees WHERE job_id = p_job AND salary > p_salary; BEGIN FOR v_emp_record IN cur_emps('IT_PROG', 10000) LOOP DBMS_OUTPUT.PUT_LINE(v_emp_record.employee_id ||' ' || v_emp_record.last_name); END LOOP; END; 88 89 Declaring a Cursor with the FOR UPDATE Syntax • When we declare a cursor FOR UPDATE, each row is locked as we open the cursor. • This prevents other users from modifying the rows while our cursor is open. • It also allows us to modify the rows ourselves using a … WHERE CURRENT OF … clause. CURSOR cursor_name IS SELECT ... FROM ... FOR UPDATE [OF column_reference][NOWAIT | WAIT n]; • This does not prevent other users from reading the rows. 90 Declaring a Cursor with the FOR UPDATE Clause • column_reference is a column in the table whose rows we need to lock. CURSOR cursor_name IS SELECT ... FROM ... FOR UPDATE [OF column_reference][NOWAIT | WAIT n]; • If the rows have already been locked by another session: – NOWAIT returns an Oracle server error immediately – WAIT n waits for n seconds, and returns an Oracle server error if the other session is still locking the rows at the end of that time. 91 NOWAIT Keyword in the FOR UPDATE Clause Example • The optional NOWAIT keyword tells the Oracle server not to wait if any of the requested rows have already been locked by another user. • Control is immediately returned to your program so that it can do other work before trying again to acquire the lock. • If you omit the NOWAIT keyword, then the Oracle server waits indefinitely until the rows are available. DECLARE CURSOR cur_emps IS SELECT employee_id, last_name FROM employees WHERE department_id = 80 FOR UPDATE NOWAIT; ... 92 NOWAIT Keyword in the FOR UPDATE Clause • If the rows are already locked by another session and you have specified NOWAIT, then opening the cursor will result in an error. • You can try to open the cursor later. • You can use WAIT n instead of NOWAIT and specify the number of seconds to wait and check whether the rows are unlocked. • If the rows are still locked after n seconds, then an error is returned. 93 FOR UPDATE OF column-name Example • If the cursor is based on a join of two tables, we may want to lock the rows of one table but not the other. • To do this, we specify any column of the table we want to lock. DECLARE CURSOR emp_cursor IS SELECT e.employee_id, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id AND department_id = 80 FOR UPDATE OF salary; ... 94 WHERE CURRENT OF Clause Syntax • The WHERE CURRENT OF clause is used in conjunction with the FOR UPDATE clause to refer to the current row (the most recently FETCHed row) in an explicit cursor. • The WHERE CURRENT OF clause is used in the UPDATE or DELETE statement, whereas the FOR UPDATE clause is specified in the cursor declaration. WHERE CURRENT OF cursor-name; • cursor_name Is the name of a declared cursor (The cursor must have been declared with the FOR UPDATE clause.) 95 WHERE CURRENT OF Clause • You can use WHERE CURRENT OF for updating or deleting the current row from the corresponding database table. • This enables you to apply updates and deletes to the row currently being addressed, without the need to use a WHERE clause. • You must include the FOR UPDATE clause in the cursor query so that the rows are locked on OPEN. WHERE CURRENT OF cursor-name; 96 WHERE CURRENT OF Clause Example Use cursors to update or delete the current row. • Include the FOR UPDATE clause in the cursor query to lock the rows first. • Use the WHERE CURRENT OF clause to reference the current row from an explicit cursor. UPDATE employees SET salary = ... WHERE CURRENT OF cur_emps; 97 NOWAIT, FOR UPDATE, and WHERE CURRENT OF Clause In this example, we don’t need a column-reference in the FOR UPDATE clause because the cursor is not based on a join. DECLARE CURSOR cur_emps IS SELECT employee_id, salary FROM my_employees WHERE salary <= 20000 FOR UPDATE NOWAIT; v_emp_rec cur_emps%ROWTYPE; BEGIN OPEN cur_emps; LOOP FETCH cur_emps INTO v_emp_rec; EXIT WHEN cur_emps%NOTFOUND; UPDATE my_employees SET salary = v_emp_rec.salary*1.1 WHERE CURRENT OF cur_emps; END LOOP; CLOSE cur_emps; END; 98 FOR UPDATE Second Example • FOR UPDATE OF salary locks only the MY_EMPLOYEES rows, not the MY_DEPARTMENTS rows. • Note that we update the table-name, not the cursorname! DECLARE CURSOR cur_eds IS SELECT employee_id, salary, department_name FROM my_employees e, my_departments d WHERE e.department_id = d.department_id FOR UPDATE OF salary NOWAIT; BEGIN FOR v_eds_rec IN cur_eds LOOP UPDATE my_employees SET salary = v_eds_rec.salary * 1.1 WHERE CURRENT OF cur_eds; END LOOP; END; 99 Reference Oracle Academy PLSQL S5L4 Cursor with Parameters PLSQL S5L5 Using Cursor for Updates Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Define PL/SQL exceptions. • Recognize unhandled exceptions. What is an Exception? • An exception occurs when an error is discovered during the execution of a program that disrupts the normal operation of the program. • There are many possible causes of exceptions: a user makes a spelling mistake while typing; a program does not work correctly; an advertised web page does not exist; and so on. • Can you think of errors that you have come across while using a web site or application? 5 What is an Exception? Some examples of errors you may have seen: • Entering an incorrect username and/or password • Forgetting to include the @ in an email address • Entering a credit card number incorrectly • Entering an expiration date that has passed • Selecting more than one row into a single variable • Receiving “no rows returned” from a select statement 6 Exceptions in PL/SQL • This example works fine. But what if v_country_name was 'Korea, South' instead of 'Republic of Korea?' DECLARE v_country_name countries.country_name%TYPE := 'Republic of Korea'; v_elevation countries.highest_elevation%TYPE; BEGIN SELECT highest_elevation INTO v_elevation FROM countries WHERE country_name = v_country_name; DBMS_OUTPUT.PUT_LINE(v_elevation); END; 1950 Statement processed. 7 Exceptions in PL/SQL • When our v_country_name is not found, our code results in an error. DECLARE v_country_name countries.country_name%TYPE :='Korea, South'; v_elevation countries.highest_elevation%TYPE; BEGIN SELECT highest_elevation INTO v_elevation FROM countries WHERE country_name = v_country_name; END; 8 Exceptions in PL/SQL • The code does not work as expected. • No data was found for 'Korea, South' because the country name is actually stored as 'Republic of Korea.' • This type of error in PL/SQL is called an exception. • When code does not work as expected, PL/SQL raises an exception. • When an exception occurs, we say that an exception has been "raised." • When an exception is raised, the rest of the execution section of the PL/SQL block is not executed. 9 What Is an Exception Handler? • An exception handler is code that defines the recovery actions to be performed when an exception is raised (that is, when an error occurs). • When writing code, programmers need to anticipate the types of errors that can occur during the execution of that code. • They need to include exception handlers in their code to address these errors. In a sense, exception handlers allow programmers to "bulletproof" their code. 10 What Is an Exception Handler? • What types of errors might programmers want to account for by using an exception handler? • System errors (for example, a hard disk is full) • Data errors (for example, trying to duplicate a primary key value) • User action errors (for example, data entry error) • Many other possibilities! 11 Why is Exception Handling Important? Some reasons include: •Protects the user from errors (frequent errors, unhelpful error messages, and software crashes can frustrate users/customers, and this is not good). •Protects the database from errors (data can be lost or overwritten). •Errors can be costly, in time and resources (processes may slow as operations are repeated or errors are investigated). 12 Handling Exceptions with PL/SQL • A block always terminates when PL/SQL raises an exception, but you can specify an exception handler to perform final actions before the block ends. DECLARE v_country_name countries.country_name%TYPE := 'Korea, South'; v_elevation countries.highest_elevation%TYPE; BEGIN SELECT highest_elevation INTO v_elevation FROM countries WHERE country_name = v_country_name; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE ('Country name, ' || v_country_name || ', cannot be found. Re-enter the country name using the correct spelling.'); END; 13 Handling Exceptions with PL/SQL • The exception section begins with the keyword EXCEPTION. DECLARE v_country_name countries.country_name%TYPE := 'Korea, South'; v_elevation countries.highest_elevation%TYPE; BEGIN SELECT highest_elevation INTO v_elevation FROM countries WHERE country_name = v_country_name; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE ('Country name, ' || v_country_name || ', cannot be found. Re-enter the country name using the correct spelling.'); END; 14 Handling Exceptions with PL/SQL • When an exception is handled, the PL/SQL program does not terminate abruptly. • When an exception is raised, control immediately shifts to the exception section and the appropriate handler in the exception section is executed. • The PL/SQL block terminates with normal, successful completion. Country name, Korea, South, cannot be found. Re-enter the country name using the correct spelling. Statement processed. 15 Handling Exceptions with PL/SQL • The code at point A does not execute because the SELECT statement failed. DECLARE v_country_name countries.country_name%TYPE := 'Korea, South'; v_elevation countries.highest_elevation%TYPE; BEGIN SELECT highest_elevation INTO v_elevation FROM countries WHERE country_name = v_country_name; DBMS_OUTPUT.PUT_LINE(v_elevation); -- Point A EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE ('Country name, ' || v_country_name || ', cannot be found. Re-enter the country name using the correct spelling.'); END; • When an exception is raised, the rest of the executable section of the block is NOT executed; instead, the EXCEPTION section is searched for a suitable handler. 16 Handling Exceptions with PL/SQL • The following is another example. • The select statement in the block is retrieving the last_name of Stock Clerks. DECLARE v_lname VARCHAR2(15); BEGIN SELECT last_name INTO v_lname FROM employees WHERE job_id = 'ST_CLERK'; DBMS_OUTPUT.PUT_LINE('The last name of the ST_CLERK is : '||v_lname); END; ORA-01422: exact fetch returns more than requested number of rows • However, an exception is raised because more than one ST_CLERK exists in the data. 17 Handling Exceptions with PL/SQL • The following code includes a handler for the predefined Oracle server error called TOO_MANY_ROWS. • You will learn more about predefined server errors in the next lesson. DECLARE v_lname employees.last_name%TYPE; BEGIN SELECT last_name INTO v_lname FROM employees WHERE job_id = 'ST_CLERK'; DBMS_OUTPUT.PUT_LINE('The last name of the ST_CLERK is: ' || v_lname); EXCEPTION WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE ('Your select statement retrieved multiple rows. Consider using a cursor.'); END; 18 Trapping Exceptions • You can handle or "trap" any error by including a corresponding handler within the exceptionhandling section of the PL/SQL block. • Syntax: EXCEPTION WHEN exception1 [OR exception2 . . .] THEN statement1; statement2; . . . [WHEN exception3 [OR exception4 . . .] THEN statement1; statement2; . . .] [WHEN OTHERS THEN statement1; statement2; . . .] 19 Trapping Exceptions • Each handler consists of a WHEN clause, which specifies an exception name (exception1, exception 2, etc.), followed by THEN and one or more statements to be executed when that exception is raised (statement1, statement 2, etc.). • You can include any number of handlers within an EXCEPTION section to handle different exceptions. EXCEPTION WHEN exception1 [OR exception2 . . .] THEN statement1; statement2; . . . [WHEN OTHERS THEN statement1; statement2; . . .] 20 Trapping • In the syntax, OTHERS is an optional exceptionhandling clause that traps any exceptions that have not been explicitly handled. EXCEPTION WHEN exception1 [OR statement1; statement2; . . . [WHEN OTHERS THEN statement1; statement2; . . .] exception2 . . .] THEN 21 The OTHERS Exception Handler • The exception-handling section traps only those exceptions that are specified; any other exceptions are not trapped unless you use the OTHERS exception handler. • The OTHERS handler traps all the exceptions that are not already trapped. • If used, OTHERS must be the last exception handler that is defined. 22 The OTHERS Exception Handler • Consider the following example: BEGIN ... ... EXCEPTION WHEN NO_DATA_FOUND statement1; statement2; ... WHEN TOO_MANY_ROWS statement3; statement4; ... WHEN OTHERS THEN statement5; statement6; ... END; THEN THEN 23 Guidelines for Trapping Exceptions • Follow these guidelines when trapping exceptions: • Always add exception handlers whenever there is a possibility of an error occurring. • Errors are especially likely during calculations, string manipulation, and SQL database operations. • Handle named exceptions whenever possible, instead of using OTHERS in exception handlers. • Learn the names and causes of the predefined exceptions. • Test your code with different combinations of bad data to see what potential errors arise. 24 Guidelines for Trapping Exceptions • Write out debugging information in your exception handlers. • Carefully consider whether each exception handler should commit the transaction, roll it back, or let it continue. • No matter how severe the error is, you want to leave the database in a consistent state and avoid storing any bad data. 25 Reference PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell Oracle Academy PLSQL S7L1 Handling Exceptions Copyright © 2019, Oracle and/or its affiliates. All rights reserved. INTENDED LEARNING OUTCOME • List and use different types of PL/SQL exception handlers. • Trap unanticipated errors. • Describe the effect of exception propagation in nested blocks. • Customize PL/SQL exception messages. Exception Types • Oracle Server Errors : Predefined and non- predefined An Oracle Server error is an error which is recognized and raised automatically by the Oracle server. • User-defined exception 31 Handling Exceptions with PL/SQL • There are two methods for raising an exception: • Implicitly (automatically) by the Oracle server: – An Oracle error occurs and the associated exception is raised automatically. – For example, if the error ORA-01403 occurs when no rows are retrieved from the database in a SELECT statement, then PL/SQL raises the exception NO_DATA_FOUND. 32 Handling Exceptions with PL/SQL • Explicitly by the programmer: – Depending on the business functionality your program is implementing, you might have to explicitly raise an exception. – You raise an exception explicitly by issuing the RAISE statement within the block. – The exception being raised can be either user-defined or predefined. – User-defined exceptions are explained in the next lesson. 33 Two Types of Oracle Server Errors • When an Oracle server error occurs, the Oracle server automatically raises the associated exception, skips the rest of the executable section of the block, and looks for a handler in the exception section. • As mentioned earlier, Oracle server errors can be predefined or non-predefined. 10 Two Types of Oracle Server Errors • Predefined Oracle server errors: • Each of these errors has a predefined name, in addition to a standard Oracle error number (ORA#####) and message. • For example, if the error ORA-01403 occurs when no rows are retrieved from the database in a SELECT statement, then PL/SQL raises the predefined exception NO_DATA_FOUND. 35 Two Types of Oracle Server Errors • Non-predefined Oracle server errors: • Each of these errors has a standard Oracle error number (ORA-#####) and error message, but not a predefined name. • You declare your own names for these so that you can reference these names in the exception section. 36 Trapping Predefined Oracle Server Errors • Reference the predefined name in the exception handling routine. • Sample predefined exceptions: – NO_DATA_FOUND – TOO_MANY_ROWS – INVALID_CURSOR – ZERO_DIVIDE – DUP_VAL_ON_INDEX 37 Trapping Predefined Oracle Server Errors • For a complete list of predefined exceptions, see the PL/SQL User’s Guide and Reference. 38 Trapping Predefined Oracle Server Errors • The following example uses the TOO_MANY_ROWS predefined Oracle server error. • Note that it is not declared in the DECLARATION section. DECLARE v_lname VARCHAR2(15); BEGIN SELECT last_name INTO v_lname FROM employees WHERE job_id = 'ST_CLERK'; DBMS_OUTPUT.PUT_LINE('The last name of the ST_CLERK is: ' || v_lname); EXCEPTION WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE ('Your select statement retrieved multiple rows. Consider using a cursor.'); END; 39 Trapping Several Predefined Oracle Server Errors • This example handles TOO_MANY_ROWS and NO_DATA_FOUND, with an OTHERS handler in case any other error occurs. DECLARE v_lname VARCHAR2(15); BEGIN SELECT last_name INTO v_lname FROM employees WHERE job_id = 'ST_CLERK'; the ST_CLERK is: DBMS_OUTPUT.PUT_LINE('The last name '||v_lname); of EXCEPTION WHEN TOO_MANY_ROWS THEN found multiple DBMS_OUTPUT.PUT_LINE ('Select rows'); statement WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE ('Select found no rows'); statement WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE ('Another type of error occurred'); END; 40 Trapping Non-Predefined Oracle Server Errors • Non-predefined exceptions are similar to predefined exceptions, except they do not have predefined names. • They do have a standard Oracle error number (ORA#####) and error message. • To use specific handlers (rather than handling through an OTHERS clause), you create your own names for them in the DECLARE section and associate the names with the specific ORA-##### numbers using the PRAGMA EXCEPTION_INIT function. 41 Trapping Non-Predefined Oracle Server Errors • You can trap a non-predefined Oracle server error by declaring it first. • The declared exception is raised implicitly. In PL/SQL, the PRAGMA EXCEPTION_INIT tells the compiler to associate an exception name with a specific Oracle error number. • This allows you to refer to any Oracle Server exception by a name and to write a specific handler for it. 42 Non-Predefined Error • Examine the following example. BEGIN INSERT INTO departments (department_id, department_name) VALUES (280, NULL); END; • The code above results in the error message below. ORA-01400: cannot insert NULL into ("US_1217_S19_PLSQL"."DEPARTMENTS"."DEPARTMENT_NAME") 43 Non-Predefined Error • The INSERT statement tries to insert the value NULL for the department_name column of the departments table. • However, the operation is not successful because department_name is a NOT NULL column. • There is no predefined error name for violating a NOT NULL constraint. • The following slides will demonstrate how to "handle" non-predefined exceptions. 20 Non-Predefined Error • Declare the name of the exception in the declarative section. DECLARE 1 e_insert_excep EXCEPTION; PRAGMA EXCEPTION_INIT(e_insert_excep, -01400); BEGIN Syntax: INSERT INTO departments (department_id, department_name) VALUES (280, NULL); exception_name EXCEPTION; EXCEPTION WHEN e_insert_excep THEN DBMS_OUTPUT.PUT_LINE('INSERT FAILED'); END; 45 Non-Predefined Error • Associate the declared exception name with the standard Oracle server error number using the PRAGMA EXCEPTION_INIT function. DECLARE e_insert_excep EXCEPTION; PRAGMA EXCEPTION_INIT(e_insert_excep, -01400); BEGIN 2 INSERT INTO departments (department_id, department_name) VALUES (280, NULL); excep EXCEPTION Syntax: WHEN e_insert_ UT_LINE('INSERT FAILED'); THEN PRAGMA EXCEPTION_INIT(exception_name, -number); DBMS_OUTPUT.P END; 46 Non-Predefined Error • Reference the declared exception name within a WHEN clause in the exception-handling section. DECLARE e_insert_excep EXCEPTION; PRAGMA EXCEPTION_INIT(e_insert_excep, -01400); BEGIN INSERT INTO departments (department_id, department_name) VALUES (280, NULL); EXCEPTION WHEN e_insert_excep THEN DBMS_OUTPUT.PUT_LINE('INSERT FAILED'); END; 3 47 Functions for Trapping Exceptions • When an exception occurs, you can retrieve the associated error code or error message by using two functions. • Based on the values of the code or the message, you can decide which subsequent actions to take. – SQLERRM returns character data containing the message associated with the error number. – SQLCODE returns the numeric value for the error code. (You can assign it to a NUMBER variable.) 48 Functions for Trapping Exceptions • Note : +100 is an internationally-agreed code when no rows are returned from a query. SQLCODE Value Description 0 No exception encountered 1 User defined exception +100 NO_DATA_FOUND exception Negative number Another Oracle Server error number 49 Functions for Trapping Exceptions • You cannot use SQLCODE or SQLERRM directly in an SQL statement. • Instead, you must assign their values to local variables, then use the variables in the SQL statement, as shown in the following example: DECLARE v_error_code NUMBER; v_error_message VARCHAR2(255); BEGIN ... EXCEPTION WHEN OTHERS THEN ROLLBACK; v_error_code := SQLCODE; v_error_message := SQLERRM; INSERT INTO error_log(e_user, e_date, error_code, error_message) VALUES(USER, SYSDATE, v_error_code, v_error_message); END; 50 Exception Types • This lesson discusses user-defined errors. Instructions for Handling Exception Description Predefined Oracle server error Most common PL/SQL You need not declare these errors (about 20 or so exceptions. They are predefined that are named) by the Oracle server and are raised implicitly (automatically). Non-predefined Other PL/SQL errors Oracle server error (no name) Declare within the declarative section and allow the Oracle Server to raise them implicitly (automatically). User-defined error Declare within the declarative section, and raise explicitly. Defined by the programmer 52 Trapping User-Defined Exceptions • PL/SQL allows you to define your own exceptions. • You define exceptions depending on the requirements of your application. Declare Reference Raise Declarative section Name the exception. Executable section Explicitly raise the exception by using the RAISE statement. Exception-handling section Handle the raised exception. 53 Trapping User-Defined Exceptions • One example of the need for a user-defined exception is during the input of data. • Assume your program prompts the user for a department number and name so it can update the name of the department. DECLARE v_name VARCHAR2(20):= 'Accounting'; v_deptno NUMBER := 27; BEGIN UPDATE departments SET department_name = v_name WHERE department_id = v_deptno; END; 54 Trapping User-Defined Exceptions • What happens if the user enters an invalid department number? • Oracle doesn't see this as an error. • You will need a user-defined error to catch this situation. DECLARE v_name VARCHAR2(20):= 'Accounting'; v_deptno NUMBER := 27; BEGIN UPDATE departments SET department_name = v_name WHERE department_id = v_deptno; END; 55 Trapping User-Defined Exceptions • What happens when the user enters an invalid department? • The code as written doesn't produce an Oracle error. • You need to create a user-defined error to handle this situation. • You do this by: – 1. Declaring the name of the user-defined exception within the e_invalid_department EXCEPTION; declarative section. – 2. Using the RAISE statement to raise the exception explicitly within the executable section. IF SQL%NOTFOUND THEN RAISE e_invalid_department; 56 Trapping User-Defined Exceptions • You do this by: – 3. Referencing the declared exception name within a WHEN clause in the exception-handling section. EXCEPTION WHEN e_invalid_department such department id.'); THEN DBMS_OUTPUT.PUT_LINE('No • These three "steps" are similar to what we did in the previous lesson with non-predefined Oracle errors. • The differences are, no PRAGMA EXCEPTION_INIT is required and you must explicitly raise the exception using the RAISE command. 10 Trapping User-Defined Exceptions • The completed code with the "steps" indicated. DECLARE 1 e_invalid_department EXCEPTION; v_name VARCHAR2(20):='Accounting'; v_deptno NUMBER := 27; BEGIN UPDATE departments SET department_name = v_name WHERE department_id = v_deptno; IF SQL%NOTFOUND THEN 2 RAISE e_invalid_department; END IF; EXCEPTION 3 WHEN e_invalid_department THEN DBMS_OUTPUT.PUT_LINE('No such department id.'); END; 58 The RAISE Statement • You can use the RAISE statement to raise exceptions. • Raising a user-defined exception: IF v_grand_total = 0 THEN RAISE e_invalid_total; ELSE DBMS_OUTPUT.PUT_LINE(v_num_students / v_grand_total); END IF; • Raising an Oracle server error: IF v_grand_total = 0 THEN RAISE ZERO_DIVIDE; ELSE DBMS_OUTPUT.PUT_LINE(v_num_students / v_grand_total); END IF; 59 The RAISE_APPLICATION_ERROR Procedure • You can use the RAISE_APPLICATION_ERROR procedure to return user-defined error messages from stored subprograms. • The following slides explain the syntax for using RAISE_APPLICATION_ERROR • The main advantage of using this procedure instead of RAISE, is that RAISE_APPLICATION_ERROR allows you to associate your own error number and meaningful message with the exception. 60 The RAISE_APPLICATION_ERROR Syntax • The error_number must fall between -20000 and -20999. • This range is reserved by Oracle for programmer use, and is never used for predefined Oracle server errors. • message is the user-specified message for the exception. • It is a character string up to 2,048 bytes long. RAISE_APPLICATION_ERROR (error_number, message[, {TRUE | FALSE}]); 61 The RAISE_APPLICATION_ERROR Syntax • TRUE | FALSE is an optional Boolean parameter. • If TRUE, the error is placed on the stack of previous errors. • If FALSE—the default—the error replaces all previous errors. RAISE_APPLICATION_ERROR (error_number, message[, {TRUE | FALSE}]); 62 The RAISE_APPLICATION_ERROR Usage • You can use the RAISE_APPLICATION_ERROR in two different places: • Executable section • Exception section 63 RAISE_APPLICATION_ERROR in the Executable Section • When called, the RAISE_APPLICATION_ERROR procedure displays the error number and message to the user. • This process is consistent with other Oracle server errors. DECLARE PLS_INTEGER := 123; v_mgr BEGIN DELETE FROM employees WHERE manager_id = v_mgr; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20202, 'This is not a valid manager'); END IF; END; 64 RAISE_APPLICATION_ERROR in the Exception Section DECLARE v_mgr PLS_INTEGER := 27; v_employee_id employees.employee_id%TYPE; BEGIN SELECT employee_id INTO v_employee_id FROM employees WHERE manager_id = v_mgr; DBMS_OUTPUT.PUT_LINE('Employee #' || v_employee_id || ' works for manager #' || v_mgr || '.'); EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20201, 'This manager has no employees'); WHEN TOO_MANY_ROWS THEN RAISE_APPLICATION_ERROR(20202, 'Too many employees were found.'); END; 65 Using the RAISE_APPLICATION_ERROR with a User-Defined Exception DECLARE e_name EXCEPTION; PRAGMA EXCEPTION_INIT(e_name, -20999); v_last_name employees.last_name%TYPE := 'Silly Name'; BEGIN DELETE FROM employees WHERE last_name = v_last_name; IF SQL%ROWCOUNT = 0 THEN RAISE_APPLICATION_ERROR(-20999, 'Invalid last name'); ELSE DBMS_OUTPUT.PUT_LINE(v_last_name ||' deleted'); END IF; EXCEPTION WHEN e_name THEN DBMS_OUTPUT.PUT_LINE('Valid last names are: '); FOR c1 IN (SELECT DISTINCT last_name FROM employees) LOOP DBMS_OUTPUT.PUT_LINE(c1.last_name); END LOOP; WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error deleting from employees'); END; 66 Reference Oracle Academy PLSQL S7L2 Trapping Oracle Server Exceptions PLSQL S7L3 Trapping User Defined Exceptions Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Identify the benefits of modularized and layered subprogram design. • Create and call procedures. 4 Differences Between Anonymous Blocks and Subprograms • As the word “anonymous” indicates, anonymous blocks are unnamed executable PL/SQL blocks. • Because they are unnamed, they can neither be reused nor stored in the database for later use. • While you can store anonymous blocks on your PC, the database is not aware of them, so no one else can share them. • Procedures and functions are PL/SQL blocks that are named, and they are also known as subprograms. 5 Differences Between Anonymous Blocks and Subprograms • These subprograms are compiled and stored in the database. • The block structure of the subprograms is similar to the structure of anonymous blocks. • While subprograms can be explicitly shared, the default is to make them private to the owner’s schema. • Later subprograms become the building blocks of packages and triggers. 6 Differences Between Anonymous Blocks and Subprograms • Anonymous blocks DECLARE (Optional) Variables, cursors, etc.; BEGIN (Mandatory) SQL and PL/SQL statements; EXCEPTION (Optional) WHEN exception-handling actions; END; (Mandatory) • Subprograms (procedures) CREATE [OR REPLACE] PROCEDURE name [parameters] IS|AS (Mandatory) Variables, cursors, etc.; (Optional) BEGIN (Mandatory) SQL and PL/SQL statements; EXCEPTION (Optional) WHEN exception-handling actions; END [name]; (Mandatory) 7 Differences Between Anonymous Blocks and Subprograms • The alternative to an anonymous block is a named block. How the block is named depends on what you are creating. • You can create : – a named procedure (does not return values except as out parameters) – a function (must return a single value not including out parameters) – a package (groups functions and procedures together) – a trigger 8 Differences Between Anonymous Blocks and Subprograms • The keyword DECLARE is replaced by CREATE PROCEDURE procedure-name IS | AS. • In anonymous blocks, DECLARE states, "this is the start of a block." • Because CREATE PROCEDURE states, "this is the start of a subprogram," we do not need (and must not use) DECLARE. 10 9 Differences Between Anonymous Blocks and Subprograms Anonymous Blocks Subprograms Unnamed PL/SQL blocks Named PL/SQL blocks Compiled on every execution Compiled only once, when created Not stored in the database Stored in the database Cannot be invoked by other applications They are named and therefore can be invoked by other applications Do not return values Subprograms called functions must return values Cannot take parameters Can take parameters 10 Differences Between Anonymous Blocks and Subprograms • The keyword DECLARE is replaced by CREATE PROCEDURE procedure-name IS | AS. • In anonymous blocks, DECLARE states, "this is the start of a block." • Because CREATE PROCEDURE states, "this is the start of a subprogram," we do not need (and must not use) DECLARE. 11 Benefits of Subprograms • Procedures and functions have many benefits due to the modularizing of the code: – Easy maintenance: Modifications need only be done once to improve multiple applications and minimize testing. – Code reuse: Subprograms are located in one place. • When compiled and validated, they can be used and reused in any number of applications. 12 Benefits of Subprograms • Improved data security: Indirect access to database objects is permitted by the granting of security privileges on the subprograms. • By default, subprograms run with the privileges of the subprogram owner, not the privileges of the user. • Data integrity: Related actions can be grouped into a block and are performed together (“Statement Processed”) or not at all. 13 Benefits of Subprograms • Improved performance: You can reuse compiled PL/SQL code that is stored in the shared SQL area cache of the server. • Subsequent calls to the subprogram avoid compiling the code again. • Also, many users can share a single copy of the subprogram code in memory. • Improved code clarity: By using appropriate names and conventions to describe the action of the routines, you can reduce the need for comments, and enhance the clarity of the code. 14 Procedures and Functions : Similarities • Are named PL/SQL blocks • Are called PL/SQL subprograms • Have block structures similar to anonymous blocks: – Optional parameters – Optional declarative section (but the DECLARE keyword changes to IS or AS) – Mandatory executable section – Optional section to handle exceptions • Procedures and functions can both return data as OUT and IN OUT parameters. 15 Procedures and Functions : Differences • A function MUST return a value using the RETURN statement. • A procedure can only return a value using an OUT or an IN OUT parameter. • The return statement in a function returns control to the calling program and returns the results of the function. • The return statement within a procedure is optional. It returns control to the calling program before all of the procedure's code has been executed. • Functions can be called from SQL, procedures cannot. • Functions are considered expressions, procedures are not. 16 What Is a Procedure? • A procedure is a named PL/SQL block that can accept parameters. • Generally, you use a procedure to perform an action (sometimes called a “side-effect”). • A procedure is compiled and stored in the database as a schema object. – Shows up in USER_OBJECTS as an object type of PROCEDURE – More details in USER_PROCEDURES – Detailed PL/SQL code in USER_SOURCE 17 Syntax for Creating Procedures • Parameters are optional • Mode defaults to IN • Datatype can be either explicit (for example, VARCHAR2) or implicit with %TYPE • Body is the same as an anonymous block CREATE [OR REPLACE] PROCEDURE procedure_name [(parameter1 [mode1] datatype1, parameter2 [mode2] datatype2, . . .)] IS|AS procedure_body; 18 Syntax for Creating Procedures • Use CREATE PROCEDURE followed by the name, optional parameters, and keyword IS or AS. • Add the OR REPLACE option to overwrite an existing procedure. • Write a PL/SQL block containing local variables, a BEGIN, and an END (or END procedure_name). CREATE [OR REPLACE] PROCEDURE procedure_name [(parameter1 [mode] datatype1, parameter2 [mode] datatype2, ...)] IS|AS [local_variable_declarations; …] BEGIN -- actions; END [procedure_name]; PL/SQL Block 20 19 Procedure: Example • In the following example, the add_dept procedure inserts a new department with the department_id 280 and department_name ST-Curriculum. • The procedure declares two variables, v_dept_id and v_dept_name, in the declarative section. CREATE OR REPLACE PROCEDURE add_dept IS v_dept_id dept.department_id%TYPE; v_dept_name dept.department_name%TYPE; BEGIN := 280; v_dept_id v_dept_name := 'ST-Curriculum'; dept(department_id, department_name) INSERT INTO VALUES(v_dept_id, v_dept_name); DBMS_OUTPUT.PUT_LINE('Inserted '|| SQL%ROWCOUNT ||' row.’); END; 20 Procedure: Example • The declarative section of a procedure starts immediately after the procedure declaration and does not begin with the keyword DECLARE. • This procedure uses the SQL%ROWCOUNT cursor attribute to check if the row was successfully inserted. SQL%ROWCOUNT should return 1 in this case. CREATE OR REPLACE PROCEDURE add_dept IS v_dept_id dept.department_id%TYPE; v_dept_name dept.department_name%TYPE; BEGIN v_dept_id := 280; v_dept_name := 'ST-Curriculum'; INSERT INTO dept(department_id, department_name) VALUES(v_dept_id, v_dept_name); DBMS_OUTPUT.PUT_LINE('Inserted '|| SQL%ROWCOUNT || ' row.'); END; 21 Invoking Procedures • You can invoke (execute) a procedure from: – An anonymous block – Another procedure – A calling application • Note: You cannot invoke a procedure from inside a SQL statement such as SELECT. 22 Invoking the Procedure from Application Express • To invoke (execute) a procedure in Oracle Application Express, write and run a small anonymous block that invokes the procedure. • For example: BEGIN add_dept; END; SELECT department_id, department_name FROM dept WHERE department_id=280; • The select statement at the end confirms that the row was successfully inserted. 23 Correcting Errors in CREATE PROCEDURE Statements • If compilation errors exist, Application Express displays them in the output portion of the SQL Commands window. • You must edit the source code to make corrections. • When a subprogram is CREATED, the source code is stored in the database even if compilation errors occurred. 24 Correcting Errors in CREATE PROCEDURE Statements • After you have corrected the error in the code, you need to recreate the procedure. • There are two ways to do this: – Use a CREATE OR REPLACE PROCEDURE statement to overwrite the existing code (most common). – DROP the procedure first and then execute the CREATE PROCEDURE statement (less common). 25 Saving Your Work • Once a procedure has been created successfully, you should save its definition in case you need to modify the code later. 26 Local Subprograms • When one procedure invokes another procedure, we would normally create them separately, but we can create them together as a single procedure if we like. CREATE OR REPLACE PROCEDURE subproc ... END subproc; CREATE OR REPLACE PROCEDURE mainproc ... IS BEGIN ... subproc(...); ... END mainproc; 27 Local Subprograms • All the code is now in one place, and is easier to read and maintain. • The nested subprogram's scope is limited to the procedure within which it is defined; SUBPROC can be invoked from MAINPROC, but from nowhere else. CREATE OR REPLACE PROCEDURE mainproc ... IS PROCEDURE subproc (...) IS BEGIN ... END subproc; BEGIN ... subproc(...); ... END mainproc; 30 28 Local Subprograms • Every time an employee is deleted, we need to insert a row into a logging table. • The nested procedure LOG_EMP is called a Local Subprogram. CREATE OR REPLACE PROCEDURE delete_emp (p_emp_id IN employees.employee_id%TYPE) IS PROCEDURE log_emp (p_emp IN employees.employee_id%TYPE) IS BEGIN INSERT INTO logging_table VALUES(p_emp, ...); END log_emp; BEGIN DELETE FROM employees WHERE employee_id = p_emp_id; log_emp(p_emp_id); END delete_emp; 29 Reference PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell Oracle Academy PLSQL S8L1 Creating Procedures Copyright © 2019, Oracle and/or its affiliates. All rights reserved. 30 INTENDED LEARNING OUTCOME • Use formal and actual parameters. • Use positional, named, or mixed notation for passing parameters. • Identify the available parameter-passing modes. 34 What are Parameters? • Parameters pass or communicate data between the caller and the subprogram. • You can think of parameters as a special form of a variable, whose input values are initialized by the calling environment when the subprogram is called, and whose output values are returned to the calling environment when the subprogram returns control to the caller. • By convention, parameters are often named with a “p_” prefix. 35 What Are Parameters? • The change_grade procedure accepts three parameters: p_student_id, p_class_id, and p_grade. • These parameters act like local variables in the change_grade procedure. Student id is 1023 1023 The math class id is 543 543 The new grade is B B Calling environment PROCEDURE change_grade (p_student_id IN NUMBER, p_class_id IN NUMBER, p_grade IN VARCHAR2) IS BEGIN … UPDATE grade_table SET grade = p_grade WHERE student_id = p_student_id AND class_id = p_class_id; … END; 8 36 What Are Arguments? • Parameters are commonly referred to as arguments. • However, arguments are more appropriately thought of as the actual values assigned to the parameter variables when the subprogram is called at runtime. Student id is 1023 1023 The math class id is 543 543 The new grade is B B 9 37 What Are Arguments? • Even though parameters are a kind of variable, IN parameters are treated as constants within the subprogram and cannot be changed by the subprogram. • In the previous example, 1023 is an argument passed in to the p_student_id parameter. Student id is 1023 1023 The math class id is 543 543 The new grade is B B 10 38 Creating Procedures with Parameters • The example shows a procedure with two parameters. • Running this first statement creates the raise_salary procedure in the database. • The second example executes the procedure, passing the arguments 176 and 10 to the two parameters. CREATE OR REPLACE PROCEDURE raise_salary (p_id IN my_employees.employee_id%TYPE, p_percent IN NUMBER) IS BEGIN UPDATE my_employees SET salary = salary * (1 + p_percent/100) WHERE employee_id = p_id; END raise_salary; BEGIN raise_salary(176, 10); END; 39 Invoking Procedures with Parameters • Create an anonymous block and use a direct call inside the executable section of the block. • Where you want to call the new procedure, enter the procedure name and parameter values (arguments). • For example: BEGIN raise_salary (176, 10); END; • You must enter the arguments in the same order as they are declared in the procedure. 40 Invoking Procedures with Parameters • To invoke a procedure from another procedure, use a direct call inside an executable section of the block. • At the location of calling the new procedure, enter the procedure name and parameter arguments. CREATE OR REPLACE PROCEDURE process_employees IS CURSOR emp_cursor IS SELECT employee_id FROM my_employees; BEGIN FOR v_emp_rec IN emp_cursor LOOP raise_salary(v_emp_rec.employee_id, 10); END LOOP; END process_employees; 13 41 Types of Parameters • There are two types of parameters: Formal and Actual. • A parameter-name declared in the procedure heading is called a formal parameter. • The corresponding parameter-name (or value) in the calling environment is called an actual parameter. 14 42 Types of Parameters • In the following example, can you identify which parameter is the formal parameter and which parameter is the actual parameter? CREATE OR REPLACE PROCEDURE fetch_emp (p_emp_id IN employees.employee_id%TYPE) IS ... END; /* Now call the procedure from an anonymous block or subprogram */ BEGIN ... fetch_emp(v_emp_id); ... END; 15 43 Formal Parameters • Formal parameters are variables that are declared in the parameter list of a subprogram specification. • In the following example, in the procedure raise_sal, the identifiers p_id and p_sal represent formal parameters. CREATE PROCEDURE raise_sal(p_id IN NUMBER, p_sal IN NUMBER) IS BEGIN ... END raise_sal; • Notice that the formal parameter data types do not have sizes. • For instance p_sal is NUMBER, not NUMBER(6,2). 44 Actual Parameters • Actual parameters can be literal values, variables, or expressions that are sent to the parameter list of a called subprogram. • In the following example, a call is made to raise_sal, where the a_emp_id variable is the actual parameter for the p_id formal parameter, and 100 is the argument (the actual passed value). a_emp_id := 100; raise_sal(a_emp_id, 2000); 45 Actual Parameters Actual parameters: •Are associated with formal parameters during the subprogram call •Can also be expressions, as in the following example: raise_sal(a_emp_id, v_raise + 100); 46 Formal and Actual Parameters • The formal and actual parameters should be of compatible data types. • If necessary, before assigning the value, PL/SQL converts the data type of the actual parameter value to that of the formal parameter. 47 Formal and Actual Parameters • For instance, you can pass in a salary of '1000.00' in single quotes, so it is coming in as the letter 1 and the letters zero, etc., which get converted into the number one thousand. • This is slower and should be avoided if possible. • You can find out the data types that are expected by using the command DESCRIBE proc_name. 20 48 49 Procedural Parameter Modes • Parameter modes are specified in the formal parameter declaration, after the parameter name and before its data type. • Parameter-passing modes: – An IN parameter (the default) provides values for a subprogram to process. – An OUT parameter returns a value to the caller. – An IN OUT parameter supplies an input value, which can be returned (output) as a modified value. 50 What are Parameter Modes? Modes Calling environment IN (default) OUT IN OUT Procedure 51 Default Mode: IN • The IN mode is the default if no mode is specified. • IN parameters can only be read within the procedure. • They cannot be modified. CREATE PROCEDURE procedure(param [mode] datatype) ... CREATE OR REPLACE PROCEDURE raise_salary IN my_employees.employee_id%TYPE, (p_id p_percent IN NUMBER) IS BEGIN UPDATE my_employees SET salary = salary * (1 + p_percent/100) WHERE employee_id = p_id; END raise_salary; 52 Using OUT Parameters: Example CREATE OR REPLACE PROCEDURE query_emp (p_id IN employees.employee_id%TYPE, p_name OUT employees.last_name%TYPE, p_salary OUT employees.salary%TYPE) IS BEGIN SELECT last_name, salary INTO p_name, p_salary FROM employees WHERE employee_id = p_id; END query_emp; DECLARE employees.last_name%TYPE; a_emp_name employees.salary%TYPE; a_emp_sal BEGIN query_emp(178, a_emp_name, a_emp_sal); ... END; 53 Using the Previous OUT Example • Create a procedure with OUT parameters to retrieve information about an employee. • The procedure accepts the value 178 for employee ID and retrieves the name and salary of the employee with ID 178 into the two OUT parameters. • The query_emp procedure has three formal parameters. • Two of them are OUT parameters that return values to the calling environment, shown in the code box at the bottom of the previous slide. 54 Using the Previous OUT Example • The procedure accepts an employee ID value through the p_id parameter. • The a_emp_name and a_emp_sal variables are populated with the information retrieved from the query into their two corresponding OUT parameters. • Make sure that the data type for the actual parameter variables used to retrieve values from OUT parameters has a size large enough to hold the data values being returned. 55 Viewing OUT Parameters in Application Express • Use PL/SQL variables that are displayed with calls to the DBMS_OUTPUT.PUT_LINE procedure. DECLARE a_emp_name employees.last_name%TYPE; a_emp_sal employees.salary%TYPE; BEGIN query_emp(178, a_emp_name, a_emp_sal); DBMS_OUTPUT.PUT_LINE('Name: ' || a_emp_name); DBMS_OUTPUT.PUT_LINE('Salary: ' || a_emp_sal); END; Name: Grant Salary: 7700 56 Using IN OUT Parameters: Example Calling environment p_phone_no (before the call) '8006330575' p_phone_no (after the call) '(800)633-0575' CREATE OR REPLACE PROCEDURE format_phone (p_phone_no IN OUT VARCHAR2) IS BEGIN p_phone_no := '(' || SUBSTR(p_phone_no, 1, 3) || ')' || SUBSTR(p_phone_no, 4, 3) ||'-' || SUBSTR(p_phone_no, 7); END format_phone; 57 Using the Previous IN OUT Example • Using an IN OUT parameter, you can pass a value into a procedure that can be updated within the procedure. • The actual parameter value supplied from the calling environment can return as either of the following: – The original unchanged value – A new value that is set within the procedure 58 Using the Previous IN OUT Example • The example in the previous slide creates a procedure with an IN OUT parameter to accept a 10-character string containing digits for a phone number. • The procedure returns the phone number formatted with parentheses around the first three characters and a hyphen after the sixth digit. • For example, the phone string ‘8006330575’ is returned as ‘(800)633-0575’. 59 Calling the Previous IN OUT Example • The following code creates an anonymous block that declares a_phone_no, assigns the unformatted phone number to it, and passes it as an actual parameter to the FORMAT_PHONE procedure. • The procedure is executed and returns an updated string in the a_phone_no variable, which is then displayed. DECLARE a_phone_no VARCHAR2(13); BEGIN a_phone_no := '8006330575' ; format_phone(a_phone_no); DBMS_OUTPUT.PUT_LINE('The formatted number is: ' || a_phone_no); END; 60 Syntax for Passing Parameters There are three ways of passing parameters from the calling environment: •Positional: Lists the actual parameters in the same order as the formal parameters (most common method) •Named: Lists the actual parameters in arbitrary order and uses the association operator ( ‘=>' which is an equal and an arrow together) to associate a named formal parameter with its actual parameter •Combination: Lists some of the actual parameters as positional (no special operator) and some as named (with the => operator). 61 Parameter Passing: Examples CREATE OR REPLACE PROCEDURE add_dept( p_name IN my_depts.department_name%TYPE, p_loc IN my_depts.location_id%TYPE) IS BEGIN INSERT INTO my_depts(department_id, department_name, location_id) VALUES (departments_seq.NEXTVAL, p_name, p_loc); END add_dept; • Passing by positional notation add_dept ('EDUCATION', 1400); • Passing by named notation add_dept (p_loc=>1400, p_name=>'EDUCATION'); 62 Parameter Passing: Examples CREATE OR REPLACE PROCEDURE add_dept( p_name IN my_depts.department_name%TYPE, p_loc IN my_depts.location_id%TYPE) IS BEGIN INSERT INTO my_depts(department_id, department_name, location_id) VALUES (departments_seq.NEXTVAL, p_name, p_loc); END add_dept; • Passing by combination notation add_dept ('EDUCATION', p_loc=>1400); • Note : If Combination notation is used, the positional parameters must come first before the named parameters. 63 Parameter Passing • Will the following call execute successfully? add_dept (p_loc => 1400, 'EDUCATION'); • Answer: No, because when using the combination notation, positional notation parameters must be listed before named notation parameters. 64 Parameter Passing • Will the following call execute successfully? add_dept ('EDUCATION'); ORA-06550: line 2, column 1: PLS-00306: wrong number or types of arguments in call to 'ADD_DEPT' ORA-06550: line 2, column 1: PL/SQL: Statement ignored 1.begin 2.add_dept(‘EDUCATION’); 3.end; • Answer: No. You must provide a value for each parameter unless the formal parameter is assigned a default value. 65 Parameter Passing Example The following procedure with three parameters may be called in the following ways: CREATE OR REPLACE PROCEDURE show_emps (p_emp_id IN NUMBER, p_department_id IN NUMBER, p_hiredate IN DATE)… • Positional notation : show_emps (101, 10, ’01-dec-2006’) • Named notation : show_emps(p_department_id => 10, p_hiredate => ’01-dec-1007’, p_emp_id => 101 • Combination notation : show_emps(101, p_hiredate => ’01-dec-2007’, p_department_id = 10) 66 Using the DEFAULT Option for IN Parameters • You can assign a default value for formal IN parameters. • This provides flexibility when passing parameters. CREATE OR REPLACE PROCEDURE add_dept( p_name my_depts.department_name%TYPE := 'Unknown', p_loc my_depts.location_id%TYPE DEFAULT 1400) IS BEGIN INSERT INTO my_depts (...) VALUES (departments_seq.NEXTVAL, p_name, p_loc); END add_dept; • Using the DEFAULT keyword makes it easier to identify that a parameter has a default value. 67 Using the DEFAULT Option for IN Parameters • The code on the previous slide shows two ways of assigning a default value to an IN parameter. • The two ways shown use: – The assignment operator (:=), as shown for the p_name parameter – The DEFAULT keyword option, as shown for the p_loc parameter 68 Using the DEFAULT Option for Parameters • On the following slide, three ways of invoking the add_dept procedure are displayed: • The first example uses the default values for each parameter. • The second example illustrates a combination of position and named notation to assign values. In this case, using named notation is presented as an example. • The last example uses the default value for the name parameter and the supplied value for the p_loc parameter. 69 Using the DEFAULT Option for Parameters • Referring to the code on Slide #67, we know the add_dept procedure has two IN parameters and both parameters have default values. add_dept; add_dept add_dept ('ADVERTISING', p_loc (p_loc => 1400); => 1400); 70 Guidelines for Using the DEFAULT Option for Parameters • You cannot assign default values to OUT and IN OUT parameters in the header, but you can in the body of the procedure. • Usually, you can use named notation to override the default values of formal parameters. • However, you cannot skip providing an actual parameter if there is no default value provided for a formal parameter. • A parameter inheriting a DEFAULT value is different from NULL. 71 Working with Parameter Errors During Runtime • Note: All the positional parameters should precede the named parameters in a subprogram call. • Otherwise, you receive an error message, as shown in the following example: BEGIN add_dept(name =>'new dept', 'new location'); END; • The following error message is generated: ORA-06550: line 2, column 3: PLS-00306: a positional parameter association may not follow a named association ORA-06550: line 2, column 3: PL/SQL: Statement ignored 1. BEGIN 2. add_dept(name=>'new dept', 'new location'); 3. END; 72 Reference Oracle Academy PLSQL S8L2 Using Parameters in Procedures PLSQL S8L3 Passing Parameters Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell 73 INTENDED LEARNING OUTCOME • Differentiate between a procedure and a function. • Describe the uses of functions. • Create stored functions. • Invoke a function. • Remove a function. What Is a Stored Function? • A function is a named PL/SQL block (subprogram) that can accept optional IN parameters and must return exactly one value. • Functions must be called as part of a SQL or PL/SQL expression. • In SQL expressions, a function must obey specific rules to control side effects. • Avoid the following within functions: – Any kind of DML or DDL – COMMIT or ROLLBACK – Altering global variables 5 What Is a Stored Function? • Certain return types (Boolean, for example) prevent a function from being called as part of a SELECT. • In PL/SQL expressions, the function identifier acts like a variable whose value depends on the parameters passed to it. • A function must have a RETURN clause in the header and at least one RETURN statement in the executable section. 6 Syntax for Creating Functions The header for a function is like a PROCEDURE header with two differences: • The parameter mode should only be IN. • The RETURN clause is used instead of OUT mode. CREATE [OR REPLACE] FUNCTION function_name [(parameter1 [mode1] datatype1, ...)] RETURN datatype IS|AS [local_variable_declarations; …] BEGIN -- actions; RETURN expression; END [function_name]; 7 Syntax for Creating Functions • A function must return a single value. • You must provide a RETURN statement to return a value with a data type that is consistent with the function declaration type. • You create new functions using the CREATE [OR REPLACE] FUNCTION statement which can declare a list of parameters, must return exactly one value, and must define the actions to be performed by the PL/SQL block. 8 Stored Function With a Parameter: Example • Create the function: CREATE OR REPLACE FUNCTION get_sal (p_id IN employees.employee_id%TYPE) RETURN NUMBER IS v_sal employees.salary%TYPE := 0; BEGIN SELECT salary INTO v_sal FROM employees WHERE employee_id = p_id; RETURN v_sal; END get_sal; • Invoke the function as an expression or as a parameter value: ... v_salary := get_sal(100); 9 Using RETURN • You can use RETURN from the executable section and/or from the EXCEPTION section. • Create the function: CREATE OR REPLACE FUNCTION get_sal (p_id IN employees.employee_id%TYPE) RETURN NUMBER IS v_sal employees.salary%TYPE := 0; BEGIN SELECT salary INTO v_sal FROM employees WHERE employee_id = p_id; RETURN v_sal; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL; END get_sal; • Invoke the function as an expression with a bad parameter: ... v_salary := get_sal(999); 10 Ways to Invoke (or Execute) Functions With Parameters Functions can be invoked in the following ways: • As part of PL/SQL expressions – use a local variable in an anonymous block to hold the returned value from a function. • As a parameter to another subprogram – pass functions between subprograms. • As an expression in a SQL statement – invoke a function as any other single-row function in a SQL statement. 11 Invoking a Function as Part of a PL/SQL Expression • When invoking a function as part of a PL/SQL expression, you can use a local variable to store the returned result. • In this example, v_sal is the local variable in an anonymous block that stores the results returned from the get_sal function. DECLARE v_sal employees.salary%type; BEGIN v_sal := get_sal(100); ... END; 12 Invoking a Function as a Parameter in Another Subprogram • You can also invoke a function as a parameter to another subprogram. • In this example, the get_sal function with all its arguments is nested in the parameter required by the DBMS_OUTPUT.PUT_LINE procedure. ...DBMS_OUTPUT.PUT_LINE(get_sal(100)); 13 Invoking a Function as an Expression in a SQL Statement • You can also invoke a function as an expression in a SQL statement. • The following example shows how you can use a function as a single-row function in a SQL statement. SELECT job_id, get_sal(employee_id) FROM employees; • Note: The restrictions that apply to functions when used in a SQL statement are discussed in the next lesson. • If functions are designed thoughtfully, they can be powerful constructs. 14 Invoking Functions Without Parameters • Most functions have parameters, but not all. • For example, the system functions USER and SYSDATE have no parameters. • Invoke as part of a PL/SQL expression, using a local variable to obtain the result DECLARE v_today DATE; BEGIN v_today := SYSDATE; ... END; 15 Invoking Functions Without Parameters • Use as a parameter to another subprogram ...DBMS_OUTPUT.PUT_LINE(USER); • Use in a SQL statement (subject to restrictions) SELECT job_id, SYSDATE-hire_date FROM employees; 16 Benefits and Restrictions That Apply to Functions Benefits Restrictions Try things quickly: Functions allow you to temporarily display a value in a new format: a different case, annually vs. monthly (times 12), concatenated, or with substrings. PL/SQL types do not completely overlap with SQL types. What is fine for PL/SQL (for example, BOOLEAN, RECORD) might be invalid for a SELECT. Extend functionality: Add new features, such as spell checking and parsing. PL/SQL sizes are not the same as SQL sizes. For instance, a PL/SQL VARCHAR2 variable can be up to 32 KB, whereas a SQL VARCHAR2 column can be only up to 4 KB. 17 Syntax Differences Between Procedures and Functions • Procedures CREATE [OR REPLACE] PROCEDURE name [parameters] IS|AS (Mandatory) Variables, cursors, etc. (Optional) BEGIN (Mandatory) SQL and PL/SQL statements; EXCEPTION (Optional) WHEN exception-handling actions; END [name]; (Mandatory) • Functions CREATE [OR REPLACE] FUNCTION name [parameters] (Mandatory) RETURN datatype IS|AS (Mandatory) Variables, cursors, etc. (Optional) BEGIN (Mandatory) SQL and PL/SQL statements; RETURN ...; (One Mandatory, more optional) EXCEPTION (Optional) WHEN exception-handling actions; END [name]; (Mandatory) 18 Differences/Similarities Between Procedures and Functions Procedures Functions Execute as a PL/SQL statement Invoked as part of an expression Do not contain RETURN clause in the header Must contain a RETURN clause in the header May return values (if any) in output parameters (not required) Must return a single value May contain a RETURN statement without a value Must contain at least one RETURN statement • Both can have zero or more IN parameters that can be passed from the calling environment. • Both have the standard block structure including exception handling. 19 Differences Between Procedures and Functions Procedures • You create a procedure to store a series of actions for later execution. • A procedure does not have to return a value. • A procedure can call a function to assist with its actions. • Note: A procedure containing a single OUT parameter might be better rewritten as a function returning the value. 20 Differences Between Procedures and Functions Functions • You create a function when you want to compute a value that must be returned to the calling environment. • Functions return only a single value, and the value is returned through a RETURN statement. • The functions used in SQL statements cannot use OUT or IN OUT modes. • Although a function using OUT can be invoked from a PL/SQL procedure or anonymous block, it cannot be used in SQL statements. 21 Reference Oracle Academy PLSQL S9L1 Creating Functions Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Describe packages and list their components. • Create a package to group together related variables, cursors, constants, exceptions, procedures, and functions. • Designate a package construct as either public or private. • Invoke a package construct. What Are PL/SQL Packages? • PL/SQL packages are containers that enable you to group together related PL/SQL subprograms, variables, cursors, and exceptions. • For example, a Human Resources package can contain hiring and firing procedures, commission and bonus functions, and tax-exemption variables. 5 Components of a PL/SQL Package A package consists of two parts stored separately in the database: • Package specification: The interface to your applications. Package specification – It must be created first. – It declares the constructs (procedures, functions, variables, and so on) that are visible to the calling environment. • Package body: This contains the executable code of the subprograms that were declared in the package specification. – It can also contain its own variable declarations. Package body 6 Components of a PL/SQL Package • The detailed package body code is invisible to the calling environment, which can see only the specification. Package specification • If changes to the code are needed, the body can be edited and recompiled without having to edit or recompile the specification. • This two-part structure is an example of a modular programming principle called encapsulation. Package body 7 Components of a PL/SQL Package Package specification Variable_1 Procedure A declaration; Variable_2 Procedure B definition… Package body Variable_3 8 Syntax for Creating the Package Specification • To create packages, you declare all public constructs within the package specification. CREATE [OR REPLACE] PACKAGE package_name IS|AS public type and variable declarations public subprogram specifications END [package_name]; • The OR REPLACE option drops and re-creates the package specification. 9 Syntax for Creating the Package Specification CREATE [OR REPLACE] PACKAGE package_name IS|AS public type and variable declarations public subprogram specifications END [package_name]; • package_name: Specifies a name for the package that must be unique among objects within the owning schema. • Including the package name after the END keyword is optional. 10 Syntax for Creating the Package Specification CREATE [OR REPLACE] PACKAGE package_name IS|AS public type and variable declarations public subprogram specifications END [package_name]; • public type and variable declarations: Declares public variables, constants, cursors, exceptions, user-defined types, and subtypes. • Variables declared in the package specification are initialized to NULL by default. • public subprogram specifications: Declares the public procedures and/or functions in the package. 11 Creating the Package Specification • “Public” means that the package construct (variable, procedure, function, and so on) can be seen and executed from outside the package. • All constructs declared in the package specification are automatically public constructs. • For all public procedures and functions, the package specification should contain the subprogram name and associated parameters terminated by a semicolon (not the actual code of the subprogram). 12 Creating the Package Specification • The implementation (i.e., the detailed code) of a procedure or function that is declared in a package specification is done in the package body. • The next two slides show code examples. 13 Example of Package Specification: check_emp_pkg • G_MAX_LENGTH_OF_SERVICE is a constant declared and initialized in the specification. • CHK_HIREDATE and CHK_DEPT_MGR are two public procedures declared in the specification. • Their detailed code is written in the package body. CREATE OR REPLACE PACKAGE check_emp_pkg IS g_max_length_of_service CONSTANT NUMBER := 100; PROCEDURE chk_hiredate (p_date IN employees.hire_date%TYPE); PROCEDURE chk_dept_mgr (p_empid IN employees.employee_id%TYPE, p_mgr IN employees.manager_id%TYPE); END check_emp_pkg; 14 Package Specification: A Second Example Remember that a cursor is a type of variable. CREATE OR REPLACE PACKAGE manage_jobs_pkg IS g_todays_date DATE := SYSDATE; CURSOR jobs_curs IS SELECT employee_id, job_id FROM employees ORDER BY employee_id; PROCEDURE update_job (p_emp_id IN employees.employee_id%TYPE); PROCEDURE fetch_emps (p_job_id IN employees.job_id%TYPE, p_emp_id OUT employees.employee_id%TYPE); END manage_jobs_pkg; 15 Syntax for Creating the Package Body • Create a package body to contain the detailed code for all the subprograms declared in the specification. CREATE [OR REPLACE] PACKAGE BODY package_name IS|AS private type and variable declarations subprogram bodies [BEGIN initialization statements] END [package_name]; • package_name specifies a name for the package body that must be the same as its package specification. • Using the package name after the END keyword is optional. 16 Syntax for Creating the Package Body CREATE [OR REPLACE] PACKAGE BODY package_name IS|AS private type and variable declarations subprogram bodies [BEGIN initialization statements] END [package_name]; • Private types and variables, and BEGIN initialization statements, are discussed in later lessons. • subprogram bodies must contain the code of all the subprograms declared in the package specification (i.e., the public subprograms) and the code for all private subprograms. 17 Creating the Package Body When creating a package body, do the following: • Specify the OR REPLACE option to overwrite an existing package body. • Define the subprograms in an appropriate order. • The basic principle is that you must declare a variable or subprogram before it can be referenced by other components in the same package body. • Every subprogram declared in the package specification must also be included in the package body. 18 Example of Package Body: check_emp_pkg CREATE OR REPLACE PACKAGE BODY check_emp_pkg IS PROCEDURE chk_hiredate (p_date IN employees.hire_date%TYPE) IS BEGIN IF MONTHS_BETWEEN(SYSDATE, p_date) > g_max_length_of_service * 12 THEN RAISE_APPLICATION_ERROR(-20200, 'Invalid Hiredate'); END IF; END chk_hiredate; PROCEDURE chk_dept_mgr (p_empid IN employees.employee_id%TYPE, p_mgr IN employees.manager_id%TYPE) IS BEGIN ... END chk_dept_mgr; END check_emp_pkg; 19 Changing the Package Body Code • Suppose now you want to make a change to the CHK_HIREDATE procedure, for example, to raise a different error message. • You must edit and recompile the package body, but you do not need to recompile the specification. • Remember, the specification can exist without the body (but the body cannot exist without the specification). • Because the specification is not recompiled, you do not need to recompile any applications (or other PL/SQL subprograms) that are already invoking the package procedures. 20 Recompiling the Package Body: check_emp_pkg CREATE OR REPLACE PACKAGE BODY check_emp_pkg IS PROCEDURE chk_hiredate (p_date IN employees.hire_date%TYPE) IS BEGIN IF MONTHS_BETWEEN(SYSDATE, p_date) > g_max_length_of_service * 12 THEN RAISE_APPLICATION_ERROR(-20201, 'Hiredate Too Old'); END IF; END chk_hiredate; PROCEDURE chk_dept_mgr (p_empid IN employees.employee_id%TYPE, p_mgr IN employees.manager_id%TYPE) IS BEGIN ... END chk_dept_mgr; END check_emp_pkg; 21 Invoking Package Subprograms After the package is stored in the database, you can invoke subprograms stored within the same package or stored in another package. Specify the subprogram name Within the same package Subprogram; You can fully qualify a subprogram within the same package, but this is optional. package_name.subprogram; External to the package Fully qualify the (public) subprogram with its package name package_name.subprogram; Removing Packages • To remove the entire package, specification and body, use the following syntax: DROP PACKAGE package_name; • To remove only the package body, use the following syntax: DROP PACKAGE BODY package_name; • You cannot remove the package specification on its own. Describing a Package • You can DESCRIBE a package in the same way as you can DESCRIBE a table or view: DESCRIBE check_emp_pkg Object Type PACKAGE Object CHECK_EMP_PKG Package Name Procedure Argument CHECK_EMP_PKG CHK_DEPT_MGR CHK_HIREDATE In Out Datatype P_EMPID IN NUMBER P_MGR IN NUMBER P_DATE IN DATE • You cannot DESCRIBE individual packaged subprograms, only the whole package. 24 Reasons for Using Packages • Modularity: Related programs and variables can be grouped together. • Hiding information: Only the declarations in the package specification are visible to invokers. • Application developers do not need to know the details of the package body code. • Easier maintenance: You can change and recompile the package body code without having to recompile the specification. • Therefore, applications that already use the package do not need to be recompiled. 25 Reference Oracle Academy PLSQL S10L1 Creating Functions Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell INTENDED LEARNING OUTCOME • Describe database triggers and their uses. • Describe the different types of triggers. • View trigger information in the Data Dictionary • Disable and enable a database trigger • Remove a trigger from the database 4 Need For A Trigger • Let’s start with an example: A business rule states that whenever an employee’s salary is changed, the change must be recorded in a logging table. • You could create two procedures to do this: UPD_EMP_SAL to update the salary, and LOG_SAL_CHANGE to insert the row into the logging table. • You could invoke LOG_SAL_CHANGE from within UPD_EMP_SAL, or invoke LOG_SAL_CHANGE separately from the calling environment. 6 Need For A Trigger • But you do not have to do this. • Instead, you create a trigger. • The next slide shows how. 7 Example of a Simple Trigger • From now on, whenever an SQL statement updates a salary, this trigger executes automatically, inserting the row into the logging table. CREATE OR REPLACE TRIGGER log_sal_change_trigg AFTER UPDATE OF salary ON employees BEGIN INSERT INTO log_table (user_id, logon_date) VALUES (USER, SYSDATE); END; • This means the trigger automatically fires (that is, executes) whenever the triggering event (updating a salary) occurs. • Cause and effect: The event occurs, and the trigger fires. 8 What Is a Trigger? • A database trigger: • Is a PL/SQL block associated with a specific action (an event) such as a successful logon by a user, or an action taken on a database object such as a table or view • Executes automatically whenever the associated action occurs • Is stored in the database • In the example on the previous slide, the trigger is associated with this action: UPDATE OF salary ON employees PL/SQL Block association Table, View, etc. 9 What Is a Trigger? • Triggers allow specified actions to be performed automatically in the database without having to write any extra front-end application code. • The key difference between procedures/functions and triggers is that procedures/functions have to be invoked explicitly • Triggers are blocks of code that are always "listening" for something to happen in the database. • Also, since triggers are never explicitly invoked, they cannot receive parameters. 10 Database Triggers Compared to Application Triggers • Database triggers execute automatically whenever a data event (such as DML or DDL) or a system event (such as a user connecting or the DBA shutting down the database) occurs on a schema or database. • Database triggers are created and stored in the database just like PL/SQL procedures, functions, and packages. • Application triggers execute whenever a particular event occurs within an application. • They may lead to a database event, but they are not part of the database. 11 Which Events Can Cause a Database Trigger to Fire? The following events in the database can cause a trigger to fire: •DML operations on a table •DML operations on a view, with an INSTEAD OF trigger •DDL statements, such as CREATE and ALTER •Database system events, such as when a user logs on or the DBA shuts down the database 12 Types of Triggers Triggers can be either row-level or statement-level. • A row-level trigger fires once for each row affected by the triggering statement • A statement-level trigger fires once for the whole statement. 13 Possible Uses for Triggers You can use triggers to: •Enhance complex database security rules •Create auditing records automatically •Enforce complex data integrity rules •Create logging records automatically •Prevent tables from being accidentally dropped •Prevent invalid DML transactions from occurring 14 Example 1: Creating Logging Records Automatically • The Database Administrator wants to keep an automatic record (in a database table) of who logs onto the database, and when. • He/she could create the log table and a suitable trigger as follows: CREATE TABLE log_table ( user_id DATE); VARCHAR2(30), logon_date CREATE OR REPLACE TRIGGER logon_trigg AFTER LOGON ON DATABASE BEGIN INSERT INTO log_table (user_id, logon_date) VALUES (USER, SYSDATE); END; 15 Example 2: Enforcing Complex Data Integrity Rules • Imagine a business rule that states no employee’s job can be changed to a job that the employee has already done in the past. CREATE OR REPLACE TRIGGER check_sal_trigg BEFORE UPDATE OF job_id ON employees FOR EACH ROW DECLARE v_job_count INTEGER; BEGIN SELECT COUNT(*) INTO v_job_count FROM job_history WHERE employee_id = :OLD.employee_id AND job_id = :NEW.job_id; IF v_job_count > 0 THEN RAISE_APPLICATION_ERROR (-20201,'This employee has already done this job'); END IF; END; 16 Guidelines for Triggers • Do not define triggers to duplicate or replace actions you can do easily in other ways. • For example, implement simple data integrity rules using constraints, not triggers. • Excessive use of triggers can result in slower processing and complex interdependencies, which can be difficult to maintain. • Use triggers only when necessary and be aware of recursive (trigger that calls itself) and cascading effects. • Avoid lengthy trigger logic by creating stored procedures or packaged procedures that are invoked in the trigger body. 17 Comparison of Database Triggers and Stored Procedures Triggers Procedures Defined with CREATE TRIGGER Defined with CREATE PROCEDURE Data Dictionary contains source code in USER_TRIGGERS Data Dictionary contains source code in USER_SOURCE Implicitly invoked Explicitly invoked COMMIT, SAVEPOINT, and COMMIT, SAVEPOINT, and ROLLBACK are not allowed ROLLBACK are allowed 18 Privileges Needed for Triggers • To create a trigger in your own schema, you need: – CREATE TRIGGER system privilege – Normal object privileges (SELECT, UPDATE, EXECUTE, and so on) on objects in other schemas that are referenced in your trigger body – ALTER privilege on the table or view associated with the trigger. • To create triggers in other users' schemas, you need the CREATE ANY TRIGGER privilege. 20 Privileges Needed for Triggers • Statements in the trigger body use the privileges of the trigger owner (Definer's Rights), NOT the privileges of the user executing the operation that fires the trigger (Invoker's Rights). • You cannot specify Invoker’s Rights (AUTHID CURRENT_USER) for a trigger. • The next slide shows an example. 21 Privileges Needed for Triggers Example • User Monica needs to create the following trigger: CREATE OR REPLACE TRIGGER upd_tom_emp AFTER UPDATE ON tom.employees BEGIN INSERT INTO mary.log_table VALUES(USER, SYSDATE); sharon.calledproc; END; • Monica needs the following privileges: – CREATE TRIGGER – UPDATE on TOM.EMPLOYEES – INSERT on MARY.LOG_TABLE – EXECUTE on SHARON.CALLEDPROC. 22 Viewing Triggers in the Data Dictionary You can see trigger information in the following Data Dictionary views: •USER_OBJECTS: Object name and object type (as for all other object types in your schema) •USER_TRIGGERS: Detailed code and status of the trigger •USER_ERRORS: PL/SQL syntax errors (compilation errors) of the trigger •Source code for triggers is in USER_TRIGGERS not USER_SOURCE. 23 USER_TRIGGERS Data Dictionary • Column* Column Description TRIGGER_NAME Name of the trigger TRIGGER_TYPE When it fires - BEFORE, AFTER, ROW, etc. TRIGGERING_EVENT The DML operation firing the trigger TABLE_NAME Name of the associated table REFERENCING_NAMES Name used for :OLD and :NEW WHEN_CLAUSE The when_clause used STATUS The status of the trigger TRIGGER_BODY Action taken by the trigger * Not all columns are shown here 24 Viewing Trigger Information Using USER_TRIGGERS • This example shows the triggering event, timing, type of trigger, status, and detailed body code of the RESTRICT_SALARY trigger: SELECT trigger_name, trigger_type, triggering_event, table_name, status, trigger_body FROM USER_TRIGGERS WHERE trigger_name = 'RESTRICT_SALARY'; TRIGGER_NAME RESTRICT_SALARY TRIGGER_TYPE TRIGGERING_EVENT BEFORE EACH ROW INSERT OR UPDATE TABLE_NAME EMPLOYEES STATUS TRIGGER_BODY ENABLED BEGIN IF NOT (:NEW job_id IN (‘AD_PRES’, ‘AD_VP’)) AND :NEW.salary > 15000 THEN RAISE_APPLICATION_ERROR (-20202, ‘Employee cannot earn more than $15,000’); END IF; END; 25 Changing the Status of Triggers • If you need a trigger turned off temporarily, don't drop it and then recreate it, just disable it for a little while by using the ALTER TRIGGER statement. • Disable or re-enable a database trigger: ALTER TRIGGER trigger_name DISABLE | ENABLE; • Disable or re-enable all triggers for a table: ALTER TABLE table_name DISABLE | ENABLE ALL TRIGGERS; • Recompile a trigger for a table: ALTER TRIGGER trigger_name COMPILE; • When a trigger is first created, it is enabled by default. 26 Changing the Status of Triggers Why would we disable a trigger? Answer: 1. To improve performance when loading very large amounts of data into the database. For example, imagine a trigger defined as …AFTER INSERT ON bigtable FOR EACH ROW…. Now someone (maybe the DBA) inserts 10 million rows into BIGTABLE. This row trigger will fire 10 million times, slowing down the data load considerably. 2. We may disable a trigger when it references a database object that is currently unavailable due to a failed network connection, disk crash, offline data file, or offline table space. 27 Removing Triggers • To remove a trigger from the database, use the DROP TRIGGER statement: DROP TRIGGER trigger_name; • Example: DROP TRIGGER secure_emp; • Note: All triggers on a table are removed when the table is removed. 28 Reference Oracle Academy PLSQL S13L1 Introduction to Triggers PLSQL S13L5 Managing Triggers Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell 29 INTENDED LEARNING OUTCOME • Create a DML trigger • List the DML trigger components 33 What Is a DML Trigger? • A DML trigger is a trigger that is automatically fired (executed) whenever an SQL DML statement (INSERT, UPDATE, or DELETE) is executed. • You classify DML triggers in two ways: – By when they execute: BEFORE, AFTER, or INSTEAD OF the triggering DML statement. – By how many times they execute: Once for the whole DML statement (a statement trigger), or once for each row affected by the DML statement (a row trigger). 35 Creating DML Statement Triggers • The sections of a CREATE TRIGGER statement that need to be considered before creating a trigger: CREATE [OR REPLACE] TRIGGER trigger_name timing event1 [OR event2 OR event3] ON object_name trigger_body • timing: When the trigger fires in relation to the triggering event. – Values are BEFORE, AFTER, or INSTEAD OF. • event: Which DML operation causes the trigger to fire. Values are INSERT, UPDATE [OF column], and DELETE. 36 Creating DML Statement Triggers CREATE [OR REPLACE] TRIGGER trigger_name timing event1 [OR event2 OR event3] ON object_name trigger_body • object_name: The table or view associated with the trigger. • trigger_body: The action(s) performed by the trigger are defined in an anonymous block. 37 Statement Trigger Timing When should the trigger fire? •BEFORE: Execute the trigger body before the triggering DML event on a table. •AFTER: Execute the trigger body after the triggering DML event on a table. •INSTEAD OF: Execute the trigger body instead of the triggering DML event on a view. •Programming requirements will dictate which one will be used. 38 Trigger Timings and Events Examples • The first trigger executes immediately before an employee’s salary is updated: CREATE OR REPLACE TRIGGER sal_upd_trigg BEFORE UPDATE OF salary ON employees BEGIN ... END; • The second trigger executes immediately after an employee is deleted: CREATE OR REPLACE TRIGGER emp_del_trigg AFTER DELETE ON employees BEGIN ... END; 39 Trigger Timings and Events Examples • You can restrict an UPDATE trigger to updates of a specific column or columns: CREATE OR REPLACE TRIGGER sal_upd_trigg BEFORE UPDATE OF salary, commission_pct ON employees BEGIN ... END; • A trigger can have more than one triggering event: CREATE OR REPLACE TRIGGER emp_del_trigg AFTER INSERT OR DELETE OR UPDATE ON employees BEGIN ... END; 10 40 How Often Does a Statement Trigger Fire? A statement trigger: •Fires only once for each execution of the triggering statement (even if no rows are affected) •Is the default type of DML trigger •Fires once even if no rows are affected •Useful if the trigger body does not need to process column values from affected rows CREATE UPDATE INSERT (USER, END; OR REPLACE TRIGGER log_emp_changes AFTER ON employees BEGIN INTO log_emp_table (who, when) VALUES SYSDATE); 41 How Often Does a Statement Trigger Fire? • Now an UPDATE statement is executed: UPDATE employees SET ... WHERE ...; • How many times does the trigger fire, if the UPDATE statement modifies three rows? • Ten rows? • One row? • No rows? 42 And When Does the Statement Trigger Fire? • This slide shows the firing sequence for a statement trigger associated with the event INSERT INTO departments: INSERT INTO departments (department_id,department_name, location_id) VALUES (400, 'CONSULTING', 2500); Triggering action BEFORE statement trigger AFTER statement trigger 43 Trigger-Firing Sequence • A statement trigger fires only once even if the triggering DML statement affects many rows: UPDATE employees SET salary = salary * 1.1 WHERE department_id = 50; BEFORE statement trigger AFTER statement trigger 44 DML Statement Triggers Example 1 • This statement trigger automatically inserts a row into a logging table every time one or more rows are successfully inserted into EMPLOYEES. Application INSERT INTO EMPLOYEES...; EMPLOYEES table LOG_EMP trigger CREATE OR REPLACE TRIGGER log_emp AFTER INSERT ON employees BEGIN INSERT INTO log_emp_table (who, when) VALUES (USER, SYSDATE); END; 45 DML Statement Triggers Example 2 • This statement trigger automatically inserts a row into a logging table every time a DML operation is successfully executed on the DEPARTMENTS table. CREATE UPDATE INSERT (USER, END; OR REPLACE TRIGGER log_dept_changes AFTER INSERT OR OR DELETE ON DEPARTMENTS BEGIN INTO log_dept_table (which_user, when_done) VALUES SYSDATE); 46 DML Statement Triggers Example 3 • This example shows how you can use a DML trigger to enforce complex business rules that cannot be enforced by a constraint. • You want to allow INSERTs into the EMPLOYEES table during normal working days (Monday through Friday), but prevent INSERTs on the weekend (Saturday and Sunday). 47 DML Statement Triggers Example • If a user attempts to insert a row into the EMPLOYEES table during the weekend, then the user sees an error message, the trigger fails, and the triggering statement is rolled back. • The next slide shows the trigger code needed for this example. 48 DML Statement Triggers: Example 3 Application INSERT INTO EMPLOYEES...; EMPLOYEES table SECURE_EMP trigger CREATE OR REPLACE TRIGGER secure_emp BEFORE INSERT ON employees BEGIN IF TO_CHAR(SYSDATE,'DY') IN ('SAT','SUN') THEN RAISE_APPLICATION_ERROR(-20500, 'You may insert into EMPLOYEES table only during business hours'); END IF; END; 49 Testing SECURE_EMP • A user tries to INSERT a row on the weekend: INSERT INTO employees (employee_id, last_name, first_name, email, hire_date, job_id, salary, department_id) VALUES (300, 'Smith', 'Rob', 'RSMITH', SYSDATE,'IT_PROG', 4500, 60); ORA-20500: You may insert into EMPLOYEES table only during business hours. ORA-06512: at “USVA_TEST_SQL01_T01.SECURE_EMP”, line 4 ORA_04088: error during execution of trigger ‘USVA_TEST_SQL01_T01.SECURE_EMP’ 2. VALUES (300, ‘Smith’, ‘Rob’, ‘RSMITH’, SYSDATE, ‘IT_PROG’, 4500, 60); 20 50 A Final Example • This trigger does not compile successfully. • Why not? CREATE OR REPLACE TRIGGER log_dept_changes AFTER INSERT OR UPDATE OR DELETE ON DEPARTMENTS BEGIN INSERT INTO log_dept_table (which_user, when_done) VALUES (USER, SYSDATE); COMMIT; END; 51 Using Conditional Predicates • In the previous lesson, you saw a trigger that prevents INSERTs into the EMPLOYEES table during the weekend: CREATE OR REPLACE TRIGGER secure_emp BEFORE INSERT ON employees BEGIN IF TO_CHAR(SYSDATE, 'DY') IN ('SAT', 'SUN') THEN RAISE_APPLICATION_ERROR(-20500, 'You may insert into EMPLOYEES table only during business hours'); END IF; END; 53 Using Conditional Predicates CREATE OR REPLACE TRIGGER secure_emp BEFORE INSERT ON employees BEGIN IF TO_CHAR(SYSDATE, 'DY') IN ('SAT', 'SUN') THEN RAISE_APPLICATION_ERROR(-20500, 'You may insert into EMPLOYEES table only during business hours'); END IF; END; • Suppose you want to prevent any DML operation on EMPLOYEES during the weekend, but with different error messages for INSERT, UPDATE, and DELETE. • You could create three separate triggers; however, the next slide shows how to do this with a single trigger. 54 Using Conditional Predicates • The trigger keywords DELETING, INSERTING, and UPDATING are automatically declared Boolean variables which are set to TRUE or FALSE by the Oracle server. CREATE OR REPLACE TRIGGER secure_emp BEFORE INSERT OR UPDATE OR DELETE ON employees BEGIN IF TO_CHAR(SYSDATE, 'DY') IN ('SAT', 'SUN') THEN IF DELETING THEN RAISE_APPLICATION_ERROR (-20501,'You may delete from EMPLOYEES table only during business hours.’); ELSIF INSERTING THEN RAISE_APPLICATION_ERROR (-20502,'You may insert into EMPLOYEES table only during business hours.’); ELSIF UPDATING THEN RAISE_APPLICATION_ERROR (-20503,'You may update EMPLOYEES table only during business hours.’); END IF; END IF; END; 55 Using Conditional Predicates • You can use conditional predicates to test for UPDATE on a specific column: CREATE OR REPLACE TRIGGER secure_emp BEFORE UPDATE ON employees BEGIN IF UPDATING('SALARY') THEN IF TO_CHAR(SYSDATE, 'DY') IN ('SAT', THEN RAISE_APPLICATION_ERROR (-20501,'You may not update SALARY END IF; ELSIF UPDATING('JOB_ID') THEN IF TO_CHAR(SYSDATE, 'DY') = 'SUN' THEN RAISE_APPLICATION_ERROR 'SUN') on the weekend'); (-20502, 'You may not update JOB_ID on Sunday'); END IF; END IF; END; • This trigger will allow other columns of EMPLOYEES to be updated at any time. 56 Understanding Row Triggers • Remember that a statement trigger executes only once for each triggering DML statement: CREATE OR REPLACE TRIGGER log_emps AFTER UPDATE OF salary ON employees BEGIN INSERT INTO log_emp_table (who, when) VALUES (USER, SYSDATE); END; • This trigger inserts exactly one row into the log table, regardless of whether the triggering statement updates one employee, several employees, or no employees at all. 57 Understanding Row Triggers • Suppose you want to insert one row into the log table for each updated employee. • For example, if five employees were updated, you want to insert five rows into the log table so you have a record of each row that was changed. • For this, you need a row trigger. 58 Row Trigger Firing Sequence • A row trigger fires (executes) once for each row affected by the triggering DML statement, either just BEFORE the row is processed or just AFTER. • If five employees are in department 50, a row trigger associated with an UPDATE on the employees table would execute five times, storing five rows in the log file, because of the following DML statement: UPDATE employees SET salary = salary * 1.1 WHERE department_id = 50; 59 Creating a Row Trigger CREATE OR REPLACE TRIGGER log_emps AFTER UPDATE OF salary ON employees FOR EACH ROW BEGIN INSERT INTO log_emp_table (who, when) VALUES (USER, SYSDATE); END; • You specify a row trigger using FOR EACH ROW. • With this trigger, the UPDATE statement from the previous slide would cause five rows to be inserted into the log table, one for each EMPLOYEE row updated. • However, all five rows in the log table would be identical, and they would not show which employee was updated or how SALARY was changed. 60 Using :OLD and :NEW Qualifiers • When using a row trigger, you can reference and use both old and new column values in the EMPLOYEES row currently being updated. • You use :OLD.column_name to reference the preupdate value, and :NEW.column_name to reference the post-update value. 61 Using :OLD and :NEW Qualifiers • For example, if the UPDATE statement is changing an employee’s salary from $10,000 to $11,000, then while the trigger is executing: – :OLD.salary has a value of 10000 – :NEW.salary has a value of 11000. – With this information, you can now insert the data you need into the logging table. • The next slide shows how. 62 Using :OLD and :NEW Qualifiers CREATE OR REPLACE TRIGGER log_emps AFTER UPDATE OF salary ON employees FOR EACH ROW BEGIN INSERT INTO log_emp_table (who, when, which_employee, old_salary, new_salary) VALUES (USER, SYSDATE, :OLD.employee_id, :OLD.salary, :NEW.salary); END; • To log the employee_id, does it matter whether you code :OLD.employee_id or :NEW.employee_id? • Is there a difference? 63 A Second Example of Row Triggers CREATE OR REPLACE TRIGGER audit_emp_values AFTER DELETE OR INSERT OR UPDATE ON employees FOR BEGIN INSERT INTO audit_emp(user_name, time_stamp, id, old_last_name, new_last_name, old_title, new_title, old_salary, new_salary) VALUES (USER, SYSDATE, :OLD.employee_id, :OLD.last_name, :NEW.last_name, :OLD.job_id, :NEW.job_id, :OLD.salary, :NEW.salary); END; EACH ROW 64 A Second Example: Testing the audit_emp_values Trigger INSERT INTO employees (employee_id, last_name, job_id, salary, ...) VALUES (999, 'Temp emp', 'SA_REP', 1000,...); UPDATE employees SET salary = 2000, last_name = 'Smith' WHERE employee_id = 999; SELECT user_name, time_stamp, ... FROM audit_emp; 65 A Third Example of Row Triggers • Suppose you need to prevent employees who are not a President or Vice-President from having a salary of more than $15,000. CREATE OR REPLACE TRIGGER restrict_salary BEFORE INSERT OR UPDATE OF salary ON employees FOR EACH ROW BEGIN IF NOT (:NEW.job_id IN ('AD_PRES', 'AD_VP')) AND :NEW.salary > 15000 THEN RAISE_APPLICATION_ERROR (-20202, 'Employee cannot earn more than $15,000.'); END IF; END; 66 Testing the restrict_salary Trigger: UPDATE employees SET salary = 15500 WHERE last_name IN ('King','Davies'); • King is a (Vice-)President, but Davies is not. • This UPDATE statement produces the following error: • Neither EMPLOYEES row is updated, because the UPDATE statement must either succeed completely or not at all. 67 Testing the restrict_salary Trigger: • King’s salary update will be rolled back, because every SQL statement must either complete 100% successfully or not at all. This rule is called “statement-level consistency” and is a basic rule of the Oracle database. • The error message does not show which row(s) violated the check and were therefore not updated. But we could easily show this by modifying the trigger code to: … RAISE_APPLICATION_ERROR (-20202, 'Employee ' || :NEW.employee_id || ' cannot earn more than $15,000.'); 68 A Fourth Example: Implementing an Integrity Constraint With a Trigger • The EMPLOYEES table has a foreign key constraint on the DEPARTMENT_ID column of the DEPARTMENTS table. • DEPARTMENT_ID 999 does not exist, so this DML statement violates the constraint and the employee row is not updated: UPDATE employees SET department_id = 999 WHERE employee_id = 124; • You can use a trigger to create the new department automatically. The next slide shows how. 69 A Fourth Example: Creating the Trigger: CREATE OR REPLACE TRIGGER employee_dept_fk_trg BEFORE UPDATE OF department_id ON employees FOR EACH ROW DECLARE v_dept_id departments.department_id%TYPE; BEGIN SELECT department_id INTO v_dept_id FROM departments WHERE department_id = :NEW.department_id; EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO departments VALUES(:NEW.department_id, 'Dept '||:NEW.department_id, NULL, NULL); END; •Let’s test it: UPDATE employees SET department_id = 999 WHERE employee_id = 124; -- Successful after trigger is fired 70 Using the REFERENCING Clause • Look again at the first example of a row trigger: CREATE OR REPLACE TRIGGER log_emps AFTER UPDATE OF salary ON employees FOR EACH ROW BEGIN INSERT INTO log_emp_table (who, when, which_employee, old_salary, new_salary) VALUES (USER, SYSDATE, :OLD.employee_id, :OLD.salary, :NEW.salary); END; • • • • What if the EMPLOYEES table had a different name? What if it was called OLD instead? OLD is not a good name, but is possible. What would our code look like now? 71 Using the REFERENCING Clause CREATE OR REPLACE TRIGGER log_emps AFTER UPDATE OF salary ON old FOR EACH ROW BEGIN INSERT INTO log_emp_table (who, when, which_employee, old_salary, new_salary) VALUES (USER, SYSDATE, :OLD.employee_id, :OLD.salary, :NEW.salary); END; • The word "old" in this code means two things: it is a value qualifier (like :NEW) and also a table name. • The code will work, but is confusing to read. • We don't have to use :OLD and :NEW. • We can use different qualifiers by including a REFERENCING clause. 72 Using the REFERENCING Clause CREATE OR REPLACE TRIGGER log_emps AFTER UPDATE OF salary ON old REFERENCING OLD as former NEW as latter FOR EACH ROW BEGIN INSERT INTO log_emp_table (who, when, which_employee, old_salary, new_salary) VALUES (USER, SYSDATE, :former.employee_id, :former.salary, :latter.salary); END; • FORMER and LATTER are called correlation-names. • They are aliases for OLD and NEW. • We can choose any correlation names we like (for example TOM and MARY) as long as they are not reserved words. • The REFERENCING clause can be used only in row triggers. 73 Using the WHEN clause • Look at this trigger code. It records salary changes only if the new salary is greater than the old salary. CREATE OR REPLACE TRIGGER restrict_salary AFTER UPDATE of salary ON employees FOR EACH ROW BEGIN IF :NEW.salary > :OLD.salary THEN INSERT INTO log_emp_table(who,when,which_employee,old_salary,new_salary) VALUES(USER,SYSDATE,:OLD.employee_id,:OLD.salary,:NEW.salary); END IF; END; •The whole trigger body is a single IF statement. •In real life, this could be many lines of code, including CASE statements, loops, and other constructs. 74 Using the WHEN clause • We can code our IF condition in the trigger header, just before the BEGIN clause. CREATE OR REPLACE TRIGGER restrict_salary AFTER UPDATE of salary ON copy_employees FOR EACH ROW WHEN(NEW.salary > OLD.salary) BEGIN INSERT INTO log_emp_table (who,when,which_employee,old_salary,new_salary) VALUES(USER,SYSDATE,:OLD.employee_id,:OLD.salary,:NEW.salary); END; •This code is easier to read, especially if the trigger body is long and complex. •The WHEN clause can be used only with row triggers. 75 INSTEAD OF Triggers • Underlying tables cannot be updated using a Complex View (for example a view based on a join). • Suppose the EMP_DETAILS view is a complex view based on a join of EMPLOYEES and DEPARTMENTS. • The following SQL statement fails: INSERT INTO emp_details VALUES (9001, 'ABBOTT', 3000, 10, 'Administration'); • You can overcome this by creating an INSTEAD OF trigger that updates the underlying tables directly instead of trying (and failing) to update the view. • INSTEAD OF triggers are always row triggers. 76 An Example of an INSTEAD OF Trigger • Perform the INSERT into the EMP_DETAILS view that is based on the NEW_EMPS and NEW_DEPTS tables: INSERT INTO emp_details VALUES (9001, 'ABBOTT', 3000, 10, 'Administration'); 1 INSTEAD OF INSERT into EMP_DETAILS 2 INSERT into NEW_EMPS 3 UPDATE NEW_DEPTS … 77 Creating an INSTEAD OF Trigger • Step 1: Create the tables and the Complex View: CREATE TABLE new_emps AS SELECT employee_id,last_name,salary,department_id FROM employees; CREATE TABLE new_depts AS SELECT d.department_id,d.department_name, sum(e.salary) dept_sal FROM employees e, departments d WHERE e.department_id = d.department_id GROUP BY d.department_id,d.department_name; CREATE VIEW emp_details AS SELECT e.employee_id, e.last_name, e.salary, e.department_id, d.department_name FROM new_emps e, new_depts d WHERE e.department_id = d.department_id; 78 Creating an INSTEAD OF Trigger • Step 2: Create the INSTEAD OF Trigger: CREATE OR REPLACE TRIGGER new_emp_dept INSTEAD OF INSERT ON emp_details BEGIN INSERT INTO new_emps VALUES (:NEW.employee_id, :NEW.last_name,:NEW.salary, :NEW.department_id); UPDATE new_depts SET dept_sal = dept_sal + :NEW.salary WHERE department_id = :NEW.department_id; END; • INSTEAD OF triggers are always row triggers. 79 Reference Oracle Academy PLSQL S13L2 Creating DML Triggers : Part 1 PLSQL S13L3 Creating DML Triggers : Part II Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell 80 INTENDED LEARNING OUTCOME • Create a trigger for a DDL statement • Create a trigger for a database event 84 What are DDL and Database Event Triggers? • DDL triggers are fired by DDL statements: CREATE, ALTER, or DROP. • Database Event triggers are fired by non-SQL events in the database, for example: – A user connects to, or disconnects from, the database. – The DBA starts up, or shuts down, the database. – A specific exception is raised in a user session. 85 Creating Triggers on DDL Statements Syntax • ON DATABASE fires the trigger for DDL on all schemas in the database • ON SCHEMA fires the trigger only for DDL on objects in your own schema CREATE [OR REPLACE] TRIGGER trigger_name Timing [ddl_event1 [OR ddl_event2 OR ...]] ON {DATABASE|SCHEMA} trigger_body 86 Example of a DDL Trigger • You want to write a log record every time a new database object is created in your schema: CREATE OR REPLACE TRIGGER log_create_trigg AFTER CREATE ON SCHEMA BEGIN INSERT INTO log_table VALUES (USER, SYSDATE); END; • The trigger fires whenever any type of object is created. • You cannot create a DDL trigger that refers to a specific database object. 87 A Second Example of a DDL Trigger • You want to prevent any objects being dropped from your schema. CREATE OR REPLACE TRIGGER prevent_drop_trigg BEFORE DROP ON SCHEMA BEGIN RAISE_APPLICATION_ERROR (-20203, 'Attempted drop – failed’); END; • The trigger fires whenever any (type of) object is dropped. • Again, you cannot create a DDL trigger that refers to a specific database object. 88 Creating Triggers on Database Events Syntax • ON DATABASE fires the trigger for events on all sessions in the database. • ON SCHEMA fires the trigger only for your own sessions. CREATE [OR REPLACE] TRIGGER trigger_name Timing [database_event1 [OR database_event2 OR ...]] ON {DATABASE|SCHEMA} trigger_body 89 Creating Triggers on Database Events Guidelines • Remember, you cannot use INSTEAD OF with Database Event triggers. • You can define triggers to respond to such system events as LOGON, SHUTDOWN, and even SERVERERROR. • Database Event triggers can be created ON DATABASE or ON SCHEMA, except that ON SCHEMA cannot be used with SHUTDOWN and STARTUP events. 10 90 Example 1: LOGON and LOGOFF Triggers CREATE OR REPLACE TRIGGER logon_trig AFTER LOGON ON SCHEMA BEGIN INSERT INTO log_trig_table(user_id,log_date,action) VALUES (USER, SYSDATE, 'Logging on'); END; CREATE OR REPLACE TRIGGER logoff_trig BEFORE LOGOFF ON SCHEMA BEGIN INSERT INTO log_trig_table(user_id,log_date,action) VALUES (USER, SYSDATE, 'Logging off'); END; 91 Example 2: A SERVERERROR Trigger • You want to keep a log of any ORA-00942 errors that occur in your sessions: CREATE OR REPLACE TRIGGER servererror_trig AFTER SERVERERROR ON SCHEMA BEGIN IF (IS_SERVERERROR (942)) THEN INSERT INTO error_log_table ... END IF; END; • If the IS_SERVERERROR … conditional test is omitted, the trigger will fire when any Oracle server error occurs. 92 CALL Statements in a Trigger • There is no END; statement, and no semicolon at the end of the CALL statement. CREATE [OR REPLACE] TRIGGER trigger_name timing event1 [OR event2 OR event3] ON table_name [REFERENCING OLD AS old | NEW AS new] [FOR EACH ROW] [WHEN condition] CALL procedure_name CREATE OR REPLACE TRIGGER log_employee BEFORE INSERT ON EMPLOYEES CALL log_execution 93 Mutating Tables and Row Triggers • A mutating table is a table that is currently being modified by a DML statement. • A row trigger cannot SELECT from a mutating table, because it would see an inconsistent set of data (the data in the table would be changing while the trigger was trying to read it). • However, a row trigger can SELECT from a different table if needed. • This restriction does not apply to DML statement triggers, only to DML row triggers. 94 Mutating Tables and Row Triggers To avoid mutating table errors: •A row-level trigger must not query or modify a mutating table. •A statement-level trigger must not query or modify a mutating table if the trigger is fired as the result of a CASCADE delete. •Reading and writing data using triggers is subject to certain rules. The restrictions apply only to row triggers, unless a statement trigger is fired as a result of ON DELETE CASCADE. 95 Mutating Tables and Row Triggers CREATE OR REPLACE TRIGGER emp_trigg AFTER INSERT OR UPDATE OR DELETE ON employees -- EMPLOYEES is the mutating table FOR EACH ROW BEGIN SELECT … FROM employees … -- is not allowed SELECT … FROM departments … -- is allowed … END; 96 Mutating Table: Example CREATE OR REPLACE TRIGGER check_salary BEFORE INSERT OR UPDATE OF salary, job_id ON employees FOR EACH ROW DECLARE v_minsalary employees.salary%TYPE; v_maxsalary employees.salary%TYPE; BEGIN SELECT MIN(salary), MAX(salary) INTO v_minsalary, v_maxsalary FROM employees WHERE job_id = :NEW.job_id; IF :NEW.salary < v_minsalary OR :NEW.salary > v_maxsalary THEN RAISE_APPLICATION_ERROR(-20505,'Out of range'); END IF; END; 97 Mutating Table: Example UPDATE employees SET salary = 3400 WHERE last_name = 'Davies'; 98 More Possible Uses for Triggers • You should not create a trigger to do something that can easily be done in another way, such as by a check constraint or by suitable object privileges. • But sometimes you must create a trigger because there is no other way to do what is needed. • The following examples show just three situations where a trigger must be created. • There are many more! 99 Uses for Triggers: First Example • Database security (who can do what) is normally controlled by system and object privileges. • For example, user SCOTT needs to update EMPLOYEES rows: GRANT UPDATE ON employees TO scott; • But privileges alone cannot control when SCOTT is allowed to do this. • For that, we need a trigger: CREATE OR REPLACE TRIGGER weekdays_emp BEFORE UPDATE ON employees BEGIN IF (TO_CHAR (SYSDATE, 'DY') IN ('SAT','SUN')) THEN RAISE_APPLICATION_ERROR(-20506,'You may only change data during normal business hours.'); END IF; END; 20 100 Uses for Triggers: Second Example • Database integrity (what DML is allowed) is normally controlled by constraints. • For example, every employee must have a salary of at least $500: ALTER TABLE employees ADD CONSTRAINT ck_salary CHECK (salary >= 500); •If a business rule states that employees' salaries can be raised but not lowered, this constraint will not prevent an employee's salary being lowered from $700 to $600. •For that, we need a row trigger. •The code for this is shown on the next slide. 101 Uses for Triggers: Second Example • Now we don't need the constraint any more. CREATE OR REPLACE TRIGGER check_salary BEFORE UPDATE of salary ON employees FOR EACH ROW WHEN(NEW.salary < OLD.salary OR NEW.salary < 500) BEGIN RAISE_APPLICATION_ERROR (-20508,'Do not decrease salary.'); END; 102 Uses for Triggers: Second Example • Taking this example further, what if the minimum salary changes from time to time? Next year it may be $550, not $500. We don't want to drop and recreate the constraint every time. • We would create a single-row, single-column table which stores the minimum salary: CREATE TABLE minsal (min_salary NUMBER(8,2)); INSERT INTO minsal (min_salary) VALUES (500); And later, if the minimum salary changes to $550, we simply: UPDATE minsal SET min_salary = 550; 103 Uses for Triggers: Second Example • Our trigger would be coded: CREATE OR REPLACE TRIGGER check_salary BEFORE UPDATE OF salary ON employees FOR EACH ROW DECLARE v_min_sal minsal%min_salary%TYPE; BEGIN SELECT min_salary INTO v_min_sal FROM minsal; IF :NEW.salary < v_min_sal OR :NEW.salary < :OLD.salary THEN RAISE_APPLICATION_ERROR (-20508,'Do not decrease salary.’); END IF; END; 104 Uses for Triggers: Third Example • You need to create a report showing the total salary bill for a department. • You can declare and use this cursor: ... CURSOR tot_sals IS SELECT SUM(salary) FROM employees WHERE department_id = p_dept_id; ... 105 Uses for Triggers: Third Example ... CURSOR tot_sals IS SELECT SUM(salary) FROM employees WHERE department_id = p_dept_id; ... • But what if, in a large organization, there are 10,000 employees in the department? • FETCHing 10,000 rows from the EMPLOYEES table may be too slow. • The next slides show a much faster way to do this. 106 Uses for Triggers: Third Example • First, we add a new column to the DEPARTMENTS table to store the total salary bill for each department: ALTER TABLE DEPARTMENTS ADD total_salary NUMBER(12,2); • Populate this column with the current total dept salary: UPDATE departments d SET total_salary = (SELECT SUM(salary) FROM employees WHERE department_id = d.department_id); • A DML row trigger will keep this new column up to date when salaries are changed. 107 Uses for Triggers: Third Example CREATE OR REPLACE PROCEDURE increment_salary (p_id IN NUMBER, p_new_sal IN NUMBER) IS BEGIN UPDATE copy_departments SET total_salary = total_salary + NVL(p_new_sal,0) WHERE department_id = p_id; END increment_salary; CREATE OR REPLACE TRIGGER compute_salary AFTER INSERT OR UPDATE OF salary OR DELETE ON employees FOR EACH ROW BEGIN increment_salary IF DELETING THEN (:OLD.department_id,(:OLD.salary * -1)); ELSIF UPDATING THEN increment_salary (:NEW.department_id,(:NEW.salary - :OLD.salary)); ELSE increment_salary (:NEW.department_id,:NEW.salary); END IF; END; 108 Reference Oracle Academy PLSQL S13L4 Creating DDL and Database Event Triggers Copyright © 2019, Oracle and/or its affiliates. All rights reserved. PL/SQL User's Guide and Reference, Release 9.0.1 Part No. A89856-01 Copyright © 1996, 2001, Oracle Corporation. All rights reserved. Primary Authors: Tom Portfolio, John Russell 109