PL/SQL Workbook For use in association with: http://www.shu.ac.uk/schools/cms/teaching/pl3/sqlplsqlreminder.doc Version 2 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 2 What is PL/SQL? What is PL/SQL used for? PL/SQL is Oracle’s procedural extension to SQL*Plus. The advantage that SQL’s non-procedural approach has to offer - that you can state what you want done without having to state how to do it – comes at a price: loss of control. PL/SQL gives programmers some control back. As an SQL extension, PL/SQL supports the standard DML commands. PL/SQL lets you use all the SQL data manipulation, cursor control, and transaction control commands, as well as all the SQL functions, operators, and pseudo-columns. You cannot, however, use DDL commands. PL/SQL is used by many of Oracle’s tools, including Forms and Reports. You also need to master PL/SQL for writing stored procedures, functions and packages for use with Triggers. Where does PL/SQL live? The PL/SQL engine may live either client side, perhaps with a tool like SQLPLUS, or on the server. These two environments are independent. PL/SQL might be available in the Oracle Server but unavailable in tools, or the other way around. In either environment, the PL/SQL engine accepts as input any valid PL/SQL block or subprogram The engine executes procedural statements but sends SQL statements to the SQL Statement Executor in the Oracle Server. SQL is compiled and executed statement-by-statement at run time, referred to as late binding. PL/SQL, however, is processed into machinereadable p-code at compile time (early binding) and then, at run time, the PL/SQL engine simply executes the p-code. Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 3 PL/SQL Structure PL/SQL code is written in blocks, which may be nested. Each block will have the following structure: Declarative: Keyword = DECLARE. This contains the definitions of variables and other elements, such as cursors, constants, tables, etc. Executable: The only required part. The executable statements are placed between a BEGIN and END; Exception Handling: Keyword = EXCEPTION allows neat exits from problems. Blocks may be named. They may form the basis of one of 2 subprogramme types: a procedure or a function. There are loops, conditions and assignments, much as you would expect of a regular procedural 3GL language. PL/SQL is modular: several related procedures and functions may be parcelled up into a Package. The scope of a declared identifier is that region of a program unit (block, subprogram, or package) from which you can reference the identifier. Identifiers declared in a PL/SQL block are considered local to that block and global to all its sub-blocks. PL/SQL Blocks may contain : . SQL*PLUS DML and trasaction processing statements. flow of control statements such as IF...THEN...ELSE, EXIT Peter Lake Version 1 . repetition statements such as FOR...LOOP/END LOOP and WHILE...LOOP/END LOOP . assignment statements such as X:= Y + Z ; © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 4 Cursors Cursors can be define in the declaration. They act as a pointer to the current row of a result table of an SQL query. They need to be OPENed before use, rows can be FETCHed into variables, and, upon completion, a cursor should be CLOSEd to free memory. For example: DECLARE CURSOR depts_cursor IS select deptno, count(*) from emp group by deptno ; In this example we have decared a CURSOR, called depts_cursor. Once it is opened the recordset that results from the SQL query is returned to the PLSQL process for use. Attributes PL/SQL variables and cursors have attributes, which are properties that let you reference the datatype and structure of an item without needing to repeat its definition. Database columns and tables have similar attributes, which you can also use. Perhaps the most useful attribute is %TYPE which provides the datatype of a variable or database column. This is particularly useful when declaring variables that will hold database values, for example: DECLARE v_deptno emp.deptno%TYPE ; Here the variable called v_deptno is declared to be of the same type as the column called deptnlo in the EMP table. Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 5 A Worked Example Some of the topics discussed above can be found in the following code. The purpose of this simple little programme is to create a statistics table which stores statistics for each department. -- PL/SQL doesn’t support DDL so do the table creates first DROP TABLE stats ; CREATE TABLE stats (Deptno integer, StaffCount integer) ; -- begin the PL/SQL code DECLARE v_count BINARY_INTEGER ; v_deptno emp.deptno%TYPE ; CURSOR depts_cursor IS select deptno, count(*) from emp group by deptno ; BEGIN -- start by activating the cursor open depts_cursor ; LOOP -- put the values from this row into our predefined variables FETCH depts_cursor INTO v_deptno, v_count ; --exit when system variable NOTFOUND is set to true EXIT when depts_cursor%NOTFOUND ; INSERT INTO stats VALUES(v_deptno, v_count) ; END LOOP ; CLOSE depts_cursor ; END ; -- tell Oracle to compile and execute / -- check that we have the right answer select * from stats ; Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 6 Exercises Some exercises follow to get you into the swing of PL/SQL-ing. The first one has some tips on the way, but the last few are up to you! Guided Example You are asked to create a table which stores the number of employees (NOT including the boss) who earn above the average salary for the company, and the number who earn less or the same as the average, for each department. The modularity of PL/SQL lends itself to breaking larger problems down into several smaller ones. Of course, you may choose to approach this problem in whatever way you like, but here is one suggestion: 1. Write the SQL code to create the table to store the answers 2. Decide what variable(s) you will need during in the execution and place them in a DECLARE. Oracle like us to stick to a naming convention of v_ for variables. DECLARE v_below avgsal.Above%type ; v_ v_ etc.... Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 7 3. Decide what DML we need to work on (the cursors). We need 2 cursors in this example – one to calculate the average, and one to go through EMP checking each salary against the average. TIP: Try the sql first in SQLPLUSW to make sure it brings back what you need! cursor ---------- IS etc... etc....... 4. Write the BEGIN of the executable section. One tip is to write the END at the same time and add the code in between the two. 5. We need to establish what the average salary is (by opening a cursor and fetching a value INTO v_avg) 6. We need to loop through the EMP table. Write the iterative control code (LOOP....END LOOP). Don't forget your exit strategy, otherwise you will keep looping forever! 7. Dont forget you need to open and close cursors at appropriate times. 8. Apply your test to each row, and increment your variable accordingly. Then write the INSERT code to put the data into your newly created table. The format for IF blocks is: if x then y; else z; end if; An answer to the above, and all the exercises, can be found at the end of these notes Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 8 More Exercises 1. A useful attribute of a cursor is %ROWCOUNT. It records the number of rows fetched so far. (usage: cursorname%ROWCOUNT). You should use it to pick out the ten highest paid employees and store their name, job and salary in a table. 2. You a required to build a table which has 24 rows, a field called letter, and a field called count. Use PL/SQL to create this table with each letter of the alphabet. (HINT: CHR() turns an ordinal into a CHAR, and 65=A.) The count should have the count of employee names beginning with that letter (HINT: Substr()), including 0 when no names commence with the letter in question. Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Exceptions Page 9 Up until now we have only used the first two parts of a PL/SQL block. The exception part can be very useful in helping us control error situations cleanly. In PL/SQL a warning or error condition is called an exception. Some common internal exceptions have predefined names, such as ZERO_DIVIDE and NO_DATA_FOUND. When an error occurs, an exception is raised and normal execution stops with control transferring to the exception-handling part of the PL/SQL block or subprogram. Here is a sample of some exception handling – note the use of the catch-all OTHERS exception: BEGIN ..........................some lines of pl/sql code.................................. EXCEPTION WHEN DUP_VAL_ON_INDEX THEN INSERT INTO errtable VALUES (v_ename,v_position,v_sal); COMMIT; WHEN ZERO_DIVIDE THEN /* deal with these */ WHEN OTHERS THEN /* deal with these as well */ END; User Defined Exceptions. It is possible to create you own exceptions, and handle them how you see fit. You should declare your except thus: Negative_Bal EXCEPTION ; You can then choose to raise Negative_Bal in your executable code, remembering to handle it appropriately. Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Functions and Procedures Page 10 Functions and procedures are structured in the same way, except that functions have a RETURN clause. The syntax for a function is: FUNCTION name [(parameter[, parameter, ...])] RETURN datatype IS [local declarations] BEGIN executable statements [EXCEPTION exception handlers] END [name]; Generally, tools like Oracle Forms which incorporate the PL/SQL engine can store subprograms locally for later, strictly local execution. To make your code available for general use by all tools, subprograms must be stored in an Oracle database. To create subprograms and store them permanently in an Oracle database, you use the CREATE PROCEDURE and CREATE FUNCTION statements, which you can execute from SQL*Plus. As an alternative, if you are happy that you are not overwriting something valuable, there is also the CREATE OR REPLACE statement. As an example, this code will store a function for working out VAT. CREATE OR REPLACE FUNCTION vat (v_netof NUMBER) RETURN NUMBER IS BEGIN RETURN (v_netof*0.175) ; END vat ; / Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 11 Exercise Create a stored function which returns the current date concatenated with the phrase: “The date is: “ Once you get the reassuring message PL/SQL procedure successfully completed. you can test your function out from the sql> prompt thus: SQL> var test varchar2(50) SQL> execute :test:=thedateis variable --note the colon to denote global PL/SQL procedure successfully completed. SQL> print :test TEST -----------------------------------------------------------The date is: 02-Feb-04 Of course you should make your functions and procedures more robust with the use of EXCEPTIONS. Exercise Now create a procedure which puts the current count of employees into a single row single column table called EMPCOUNTER. Use the execute command to test your procedure. Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Triggers Page 12 Triggers are used to guarantee that when a specific operation is performed, related actions are carried out. A note of caution, however – many triggers make for a slow database! A database trigger can be created thus: CREATE TRIGGER triggername [BEFORE or AFTER] [EVENT] ON tablename BEGIN --do something END; The FOR EACH ROW option determines whether the trigger is a row trigger or a statement trigger and would follow the table name. One fatal trap to avoid at all costs a recursive trigger. Furthermore, don’t define triggers that merely duplicate the functionality already built into Oracle, such as referential integrity. Triggers can be turned off and on, simply by using : ALTER TRIGGER triggername DISABLE; One shortcut is ALL TRIGGERS as with: ALTER TABLE tablename ENABLE ALL TRIGGERS; Example trigger CREATE or REPLACE TRIGGER aud AFTER INSERT ON DEPT FOR EACH ROW BEGIN -- :new.deptno is the value that is entered in each row for the deptno column IF :new.deptno<50 then INSERT INTO DEPT_AUDIT values(SYSDATE,USER,'This was a sub 50 value'); END IF; END; Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 13 Exercises 1 Create a trigger which automatically updates the empcounter table whenever a new record is inserted, or a record is deleted. 2 Each time a new department is created, a audit table should have a record inserted with date, user and some text to say what has happened. Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 14 Sample Solutions Average Salary drop table avgsal ; create table avgsal(deptno integer , Above Integer, Below Integer) ; DECLARE v_below avgsal.Below%TYPE; v_above avgsal.Above%TYPE; v_avg emp.sal%TYPE ; v_lastdep integer ; v_dep integer ; v_sal emp.sal%TYPE ; CURSOR c_avg IS select avg(sal) from emp ; CURSOR c_emp IS select deptno, sal from emp where DeptNo IS NOT NULL ORDER BY deptno ; BEGIN -- first discover what the average is, and pop it in a variable OPEN c_avg ; FETCH c_avg INTO v_avg ; CLOSE c_avg ; -- we need to write a row out when the department changes v_lastdep:= 0 ; OPEN c_emp ; LOOP -- go through the emp cursor line by line FETCH c_emp INTO v_dep, v_sal; EXIT WHEN c_emp%NOTFOUND ; IF v_dep<>v_lastdep THEN -- if this isnt the first time through, write the answer out Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 15 IF v_lastdep<>0 then INSERT INTO avgsal VALUES (v_lastdep, v_above,v_below) ; END IF; v_above:=0 ; v_below:=0 ; v_lastdep:=v_dep ; END IF ; IF v_sal <= v_avg THEN v_below:=v_below+1 ; ELSE v_above:=v_above+1 ; END IF ; END LOOP; -- catch the last department INSERT INTO avgsal VALUES (v_lastdep, v_above,v_below) ; COMMIT; CLOSE c_emp; END; / select * from avgsal ; Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Top 10 Employees Page 16 drop table top_emps ; create table top_emps(name varchar2(10), job Varchar2(12), Salary Number(7,2)) ; DECLARE v_ename emp.ename%TYPE; v_position emp.job%TYPE; v_sal emp.sal%TYPE ; CURSOR cur_top_emps IS SELECT ename, job, sal FROM emp ORDER BY sal DESC ; BEGIN OPEN cur_top_emps ; LOOP FETCH cur_top_emps INTO v_ename, v_position, v_sal; EXIT WHEN cur_top_emps%ROWCOUNT > 10 ; INSERT INTO top_emps VALUES (v_ename, v_position,v_sal) ; END LOOP; COMMIT; CLOSE cur_top_emps; END; / select * from top_emps ; Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Alphabet Stats Page 17 -- PL/SQL doesnt support DDL so do the table creates first DROP TABLE stats ; CREATE TABLE stats(letter varchar2(1), Count integer) ; DECLARE v_count v_letter BINARY_INTEGER ; Varchar2(1) ; v_alpha v_ord Varchar2(1) ; BINARY_INTEGER ; CURSOR letters_cursor IS select substr(ename,1,1), count(*) from emp Group by substr(ename,1,1) ; CURSOR alphabet_cursor IS select letter from stats order by letter ; BEGIN -- create the alphabet v_ord:=64 ; LOOP v_ord:=v_ord+1 ; INSERT INTO stats values(CHR(v_ord),0) ; exit when v_ord>=90 ; END LOOP ; open alphabet_cursor ; open letters_cursor ; LOOP FETCH letters_cursor INTO v_letter, v_count ; EXIT when letters_cursor%NOTFOUND ; LOOP Fetch Alphabet_Cursor INTO V_Alpha ; IF V_Alpha=V_letter then UPDATE stats Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Page 18 SET Count = v_count WHERE letter = v_letter ; END IF ; exit when V_Alpha=V_letter ; END LOOP ; END LOOP ; COMMIT ; CLOSE letters_cursor ; CLOSE alphabet_cursor ; end ; / -- check that we have the right answer select * from stats ; The Date Is CREATE OR REPLACE FUNCTION thedateis RETURN CHAR IS BEGIN RETURN ('The date is: '||TO_CHAR(SYSDATE)) ; END thedateis ; / Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Counting Employees Page 19 -- the table only needs creating once drop table empcounter ; create table empcounter(No_Emps INTEGER) ; CREATE OR REPLACE PROCEDURE countemps IS --note there is no need for the key word DECLARE v_cownt INTEGER ; CURSOR cownt_cursor IS select count(*) from emp ; BEGIN OPEN cownt_cursor ; FETCH cownt_cursor INTO v_cownt ; CLOSE cownt_cursor ; DELETE FROM empcounter ; INSERT INTO empcounter values(v_cownt) ; END countemps ; / Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences PL/SQL Workbook Trigger to run CountEmps Page 20 CREATE OR REPLACE TRIGGER doempcount AFTER DELETE OR INSERT ON emp BEGIN countemps ; END; / Audit trail trigger CREATE OR REPLACE TRIGGER deptaudit AFTER INSERT ON dept BEGIN INSERT INTO DEPT_AUDIT values(SYSDATE,USER,'A new dept was created today'); END; / Peter Lake Version 1 © Sheffield Hallam University School of Computing and Management Sciences