Oracle Database Administration Lecture 5 Triggers PL/SQL – advanced Triggers - introduction • Triggers is a piece of code executed when specified action occurs, for example: – user inserts row into a table – user deletes something from a table – user logs in • Triggers cannot be executed as a result of SELECT statement Triggers • Triggers are often used to: – automatically populate table columns, for example generate primary key identifier from a sequence – automatically update related tables, for example: update parent table when records are inserted into the child table – guarantee that specific operation is performed, for example: automatically create records in the history tables Triggers • Do not use triggers to duplicate built-in features: – for relations use Foreign Keys – to check if single record data is valid use NOT NULL and CHECK constraints – for access control use GRANT and REVOKE Types of triggers • DML triggers on tables – UPDATE, DELETE, INSERT • INSTEAD OF triggers on views • System triggers on: – DATABASE - triggers fire for each event for each user – SCHEMA - triggers fire for each event for specific user System triggers • System triggers can be created for the following events: – DDL statements - CREATE, ALTER, DROP – Database operations: • • • • • SERVERERROR LOGON LOGOFF STARTUP SHUTDOWN System triggers • Example system trigger: CREATE OR REPLACE TRIGGER On_Logon AFTER LOGON ON USER_NAME.Schema BEGIN Do_Something; END; System triggers • Example system trigger: CREATE TRIGGER log_errors AFTER SERVERERROR ON DATABASE BEGIN IF (IS_SERVERERROR (1017)) THEN ... ELSE ... END IF; END; DML triggers - options • BEFORE/AFTER - trigger can fire before the operation or after the operation • Trigger can fire one time (statement trigger) or multiple times (row trigger) • Row trigger can have when condition • Row triggers can access new and old row values • Trigger on update can have column list Before/After triggers • Use Before triggers to: – modify values that are about to be inserted/updated • Use After triggers to: – access newly inserted/updated values (e.g. using foreign keys) • Before triggers are slightly faster than After triggers Example statement trigger CREATE OR REPLACE TRIGGER trg_1 BEFORE DELETE OR INSERT OR UPDATE ON test1 BEGIN IF INSERTING THEN INSERT INTO statement_log(log) VALUES ('inserting to test1'); ELSIF DELETING THEN INSERT INTO statement_log(log) VALUES ('deleting from test1'); ELSE INSERT INTO statement_log(log) VALUES ('updating test1'); END IF; END; Example row triggers CREATE TRIGGER order_insert BEFORE INSERT ON orders FOR EACH ROW BEGIN INSERT INTO order_history(hist_id, type, id, order_value) VALUES (hist_seq.nextval, 'insert', :new.id, :new.order_value); END; CREATE TRIGGER order_update BEFORE UPDATE ON orders FOR EACH ROW BEGIN INSERT INTO order_history(hist_id, type, id, order_value) VALUES (hist_seq.nextval, 'update', :new.id, :new.order_value); END; CREATE TRIGGER order_update BEFORE DELETE ON orders FOR EACH ROW BEGIN INSERT INTO order_history(hist_id, type, id, order_value) VALUES (hist_seq.nextval, 'update',:old.id, :old.order_value); END; Row triggers • Insert trigger has access to new values only • Delete trigger has access to old values only. New values are null and cannot be modified • Update trigger has access to new and old values. • new values can be modified in the Before trigger only • old and new values are available in both Before and After trigger • if a new value is modified in a Before trigger, modified value is visible in the After trigger Example triggers CREATE TRIGGER expensive_order BEFORE UPDATE ON orders FOR EACH ROW WHEN (new.order_value > 100000 AND old.order_value < 100000) BEGIN ... END; CREATE TRIGGER value_change BEFORE UPDATE OF order_value ON orders FOR EACH ROW BEGIN ... END; Instead of triggers • Instead of trigger is used for views which are not updateable • View is not updateable if it contains: – set operator (union, intersect etc.) – distinct operator – aggregate function (sum, max, count, etc.) – group by, order by, connect by, start with – subquery in a select list – joins with some exceptions Instead of triggers • Example Instead of trigger definition: CREATE OR REPLACE TRIGGER trigger_name INSTEAD OF INSERT ON view_name REFERENCING NEW AS n FOR EACH ROW DECLARE rowcnt number; BEGIN SELECT COUNT(*) FROM .... ... Triggers and transactions • Unless autonomous transactions are used: – trigger executes in the context of the current transaction (the transaction that executed the statement which caused the trigger to fire) – if a transaction is rolled back, trigger results are also rolled back – if a trigger raises an exception, the statement fails and statement-level rollback occurs – trigger cannot use transaction control statements (rollback, commit, savepoint) Enabling/disabling triggers • Triggers can be in enabled and disabled state • Disabled triggers do not execute • Triggers are created enabled unless the DISABLE clause is used • Commands to enable/disable triggers: ALTER TRIGGER trigger_name ENABLE; ALTER TRIGGER trigger_name DISABLE; ALTER TABLE table_name ENABLE ALL TRIGGERS; PL/SQL packages • Package is a group of: – functions – procedures – variables – cursors – type declarations • Package consists of two parts: – package specification – package body Package specification • Package specification contains declarations of public objects: functions, procedures etc. • Only public objects can be accessed from outside the package • Package specification does not contain any code, just declarations • Package specification is created using the CREATE PACKAGE command Example package specification CREATE PACKAGE pack1 IS PROCEDURE p1(param1 IN NUMBER); FUNCTION f1 RETURN VARCHAR2; var1 INTEGER; CURSOR c1 IS SELECT * FROM TEST; END; Accessing package objects BEGIN pack1.p1(0); result := pack1.f1; pack1.var1 := 1; FOR rec IN pack1.c1 LOOP ... END LOOP; END; Package body • Package body contains implementation of objects defined in the package specification • Package body is created using the CREATE PACKAGE BODY command • Package body must include implementation of all functions and procedures declared in the specification • Package body may define private functions, that will be accessible only from the package body Example package body CREATE PACKAGE BODY pack1 IS PROCEDURE p1(param1 IN NUMBER) IS BEGIN p2; -- call private procedure END; FUNCTION f1 RETURN VARCHAR2 IS BEGIN ... END; PROCEDURE p2 IS BEGIN ... END; END; RECORD type • RECORD type: • similar to C structure – contains multiple variables • must be defined as TYPE – RECORD declaration creates new type that can be later used for declaring variable of that type • RECORD can be declared: • in PACKAGE specification • in declaration part of PL/SQL block RECORD type in a package CREATE PACKAGE record_package IS TYPE DeptRec dept_id dept_name dept_loc ); END; IS RECORD ( dept.deptno%TYPE, VARCHAR2(14) DEFAULT ‘ABC’, VARCHAR2(13) RECORD type in declaration DECLARE TYPE DeptRec IS RECORD ( dept_id dept.deptno%TYPE, dept_name VARCHAR2(14), dept_loc VARCHAR2(13) ); -- type declaration recordVar DeptRec; -- variable -- declaration RECORD type • RECORD members: • can have default values • can have NOT NULL constraint • are accessed by "." operator: recordVar.member • RECORD variables: • can be used as function/procedure parameters, function result • can be used as collection elements • cannot be stored in database (table column cannot have type RECORD) RECORD type • Each table has predefined record for all table columns: DECLARE tableRec TABLE1%ROWTYPE; -- type record • RECORD can be used in SELECT INTO statement: SELECT * INTO tableRec FROM TABLE1 where ID = 1; RECORD type • RECORD can be used in UPDATE statement: UPDATE TABLE1 SET ROW = tableRec where ID = 1; • RECORD can be used in INSERT statement: INSERT INTO TABLE1 VALUES tableRec; PL/SQL exceptions • PL/SQL supports exceptions • Exceptions are thrown (raised): • as a result of executing SQL statement • as a result of calling predefined PL/SQL function procedure or package • manually by the user • Catching exceptions: • Exceptions can be caught in PL/SQL block • Uncaught exceptions are propagated to the caller PL/SQL exceptions • Exceptions and transactions: • exception in SQL statement rolls back current statement, not the entire transaction • exception thrown from PL/SQL does not cause rollback PL/SQL exceptions • Predefined exceptions: •NO_DATA_FOUND – select into statement •TOO_MANY_ROWS – select into statement •DUP_VAL_ON_INDEX – unique index violated •INVALID_NUMBER – text cannot be converted into number (e.g. TO_NUMBER) User exceptions • User can create custom exceptions: DECLARE myError EXCEPTION; BEGIN IF ... THEN RAISE myError; END IF; EXCEPTION WHEN myError THEN ROLLBACK; RAISE; END; Handling Oracle errors • Oracle reports errors as "ORA-xxxxx": ERROR at line 1: ORA-01403: no data found • Some exceptions have PL/SQL names, like NO_DATA_FOUND, TOO_MANY_ROWS • To catch exception without PL/SQL name: • find Oracle error code for that exception • declare symbolic name for that exception • catch that exception in the EXCEPTION block Handling Oracle errors • For example: deadlock exception has error code ORA-00060: ERROR at line 1: ORA-00060: deadlock detected while waiting for resource • To declare that exception, PRAGMA directive must be used with error code: -60 DECLARE deadlock_detected EXCEPTION; PRAGMA EXCEPTION_INIT( deadlock_detected, -60); Handling Oracle errors DECLARE deadlock_detected EXCEPTION; PRAGMA EXCEPTION_INIT( deadlock_detected, -60); BEGIN ... -- Some operation that -- causes an ORA-00060 error EXCEPTION WHEN deadlock_detected THEN -- handle the error END; Custom error messages • application can raise custom errors with custom error messages: raise_application_error( error_number, message[, {TRUE | FALSE}]); • error_number should be in range -20000 .. -20999 • error message can be up to 2048 characters Accessing error information • Exception handler has access to SQLCODE and SQLERRM functions • SQLCODE contains Oracle error number • SQLERRM contains error message • Example: WHEN OTHERS THEN IF SQLCODE = -60 THEN -- deadlock detected ELSE -- other error DBMS_OUTPUT.PUT_LINE(SQLCODE || ' ' || SQLERRM); END IF END; Dynamic SQL • PL/SQL enables execution of dynamic sql (SQL unknown at compilation time) • Dynamic SQL can be executed using: •EXECUTE IMMEDIATE command •OPEN FOR, FETCH, CLOSE statements •DBMS_SQL package EXECUTE IMMEDIATE • Example: EXECUTE IMMEDIATE 'DELETE FROM ' || table_name; EXECUTE IMMEDIATE 'CREATE TABLE test(id NUMBER)'; • EXECUTE IMMEDIATE • executes SQL command as text • SQL command can be dynamically built at run time EXECUTE IMMEDIATE • EXECUTE IMMEDIATE does not have access to PL/SQL variables: DECLARE v INTEGER; BEGIN EXECUTE IMMEDIATE 'DELETE FROM test WHERE id = v'; -- Run time error END; EXECUTE IMMEDIATE • EXECUTE IMMEDIATE can execute: • any DML statement • DDL statements, session control statements, system control statements • can use bind variables and return results DECLARE sql_code VARCHAR2(100) := 'UPDATE table1 SET col1 = :val'; value1 NUMBER := 10; BEGIN EXECUTE IMMEDIATE sql_code USING value1; END; DDL in PL/SQL BEGIN EXECUTE IMMEDIATE 'CREATE TABLE TAB1(ID NUMBER)'; EXECUTE IMMEDIATE 'INSERT INTO TAB1(ID) VALUES (1)'; INSERT INTO TAB1(ID) VALUES (2) – error -- table TAB1 does not exist when the code -- is compiled END; Example usage CREATE FUNCTION count_rows( table_name VARCHAR2) RETURN NUMBER CNT NUMBER; IS BEGIN EXECUTE IMMEDIATE 'SELECT COUNT(*) INTO :cnt FROM ' || table_name INTO CNT; RETURN CNT; END; Example usage CREATE PROCEDURE delete_from( table_name VARCHAR2, id NUMBER) IS BEGIN EXECUTE IMMEDIATE 'DELETE FROM ' || table_name || ' WHERE id = :id' USING ID; END; CURSOR variables • CURSOR variables are variables that contain reference to cursors (pointers to cursors) • CURSOR variables can be returned from functions and passed to other programming languages, for example: • • • • Java or C++ program calls PL/SQL procedure PL/SQL procedure opens cursor Cursor is returned back to Java or C++ (to the client) The client reads cursor data, like it does with normal SELECT statements • CURSOR variables can also be passed between PL/SQL functions CURSOR variables • Using CURSOR variable requires: • declaring CURSOR TYPE • declaring CURSOR variable • opening CURSOR • CURSOR must be closed when it is no longer required • Cursor type can be weak or strong • Structure of the strong cursor is known at compile time (number and types of columns) • Weak cursor can be opened for SQL statement returning any set of columns CURSOR type • Declaring generic cursor type: DECLARE -- weak cursor type TYPE GenericCurTyp IS REF CURSOR; BEGIN • Declaring strong cursor type: DECLARE TYPE TYPE EmpCurTyp IS REF CURSOR RETURN employees%ROWTYPE; • strong cursor type can only be used with queries that return declared type CURSOR variable • Cursor variable can be declared in DECLARE block: DECLARE cursor_var GenericCurTyp; • Can be used as function parameter: CREATE PROCEDURE proc1 ( emp_cv IN OUT EmpCurTyp) IS ... • Can be returned from a function: CREATE FUNCTION func1 RETURN GenericCurTyp IS ... Opening cursor DECLARE cursor_var GenericCurTyp; BEGIN IF .... THEN OPEN cursor_var FOR SELECT * FROM table1; ELSE OPEN cursor_var FOR 'SELECT * FROM ' || tableName || ' WHERE ID = :p' USING id; END IF; FETCH cursor_var INTO rec; CLOSE cursor_var; END; Opening cursor CREATE FUNCTION selectFunc(tableName IN VARCHAR2) RETURN GenericCurTyp DECLARE cursor_var GenericCurTyp; BEGIN OPEN cursor_var FOR 'SELECT * FROM ' || tableName; RETURN cursor_var; END; • Caller must close the returned cursor: cursorVar := selectFunc('some_table'); CLOSE cursorVar; PL/SQL collection types • PL/SQL does not support regular collections: arrays, lists, hash maps etc. • PL/SQL supports three types of collections: – index-by tables – nested tables – varrays index-by tables • Declaration: DECLARE TYPE tab IS TABLE OF VARCHAR2(100) INDEX BY BINARY-INTEGER • Characteristics: – similar to hash-tables in other languages – index-by table can store any number of elements – can be indexed by number or character type – cannot be stored in a database Using index-by tables DECLARE TYPE tab IS TABLE OF VARCHAR2(100) INDEX BY BINARY-INTEGER var tab; BEGIN var(1) := 'First item'; var(-100) := 'item before first'; var(100) := 'last item'; var(10000) := 'item after last'; IF var.exists(20) THEN ... END IF; IF var.first = -100 THEN ... END IF; END; VARRAYs • Declaration: DECLARE TYPE varray_type IS VARRAY(50) OF INTEGER; • Characteristics: – array with variable size up to the specified limit – dense array (index starts at 1) – similar to normal array in other languages – can be stored in a database – must be constructed before use (initially is NULL) Using VARRAYs DECLARE TYPE varray_type IS VARRAY(50) OF INTEGER; var varray_type; BEGIN var(1) := 10; -- ERROR – var IS NULL var := varray_type(); var.extend; -- add element var(1) := 10; -- ok var := varray_type(10, 20, 30) – ok var.extend(51) -- ERROR – limit is 50 END; Using VARRAYs in SQL CREATE TABLE tab1 ( id NUMBER PRIMARY KEY, name VARCHAR2(100), varray_col varray_type ); INSERT INTO tab1 VALUES (1, 'some name', varray_type(10, 20, 30, 40, -100)); DECLARE var varray_type(10, -100, 20, -100); BEGIN update tab1 set varray_col = var WHERE id = 1; END; Nested tables • Declaration: CREATE TYPE nested_type AS TABLE OF VARCHAR(1000); • Characteristics: – array with no size limit – initially dense, can become sparse when elements are removed – can be stored in a database – must be constructed before use (initially is NULL) Using nested tables DECLARE TYPE nested_type IS TABLE OF VARCHAR(1000); var := nested_type(); BEGIN var.extend(100); var(1) := 'first element'; var(2) := 'second element'; IF var(3) IS NULL THEN ... END IF IF var(101) IS NULL -– ERROR END IF; END IF; Using nested tables in SQL CREATE TABLE tab2 ( ID NUMBER PRIMARY KEY, NAME VARCHAR2(100), nested_col nested_type); INSERT INTO tab2 VALUES (1, 'name', nested_type('val1', 'val2', 'val3')); Using collection methods • The following methods exist for collections: • EXISTS – checks if index exists • COUNT – number of objects in the collection • LIMIT – maximum number of elements in a collection (VARRAY-s) • FIRST and LAST – lowest and highest index • PRIOR and NEXT – previous next element in the collection (useful in index-by tables) • EXTEND – adds new element (VARRAY-s and nested tables) • TRIM – remove elements from the end • DELETE – delete elements FORALL statement • bulk-bind – faster than normal FOR statement: DECLARE TYPE NumList IS VARRAY(10) OF NUMBER; depts NumList := NumList(20,30,50,55,57,60,70,75,90,92); BEGIN FORALL j IN 4..7 UPDATE emp SET sal = sal * 1.10 WHERE deptno = depts(j); END; BULK COLLECT • SELECT INTO nested table: DECLARE TYPE NumTab IS TABLE OF emp.empno%TYPE; TYPE NameTab IS TABLE OF emp.ename%TYPE; enums NumTab; -- no need to initialize names NameTab; BEGIN SELECT empno, ename BULK COLLECT INTO enums, names FROM emp; ... END; BULK COLLECT • FETCH into nested tables: DECLARE TYPE NameList IS TABLE OF emp.ename%TYPE; TYPE SalList IS TABLE OF emp.sal%TYPE; CURSOR c1 IS SELECT ename, sal FROM emp WHERE sal > 1000; names NameList; sals SalList; BEGIN OPEN c1; FETCH c1 BULK COLLECT INTO names, sals; END; BULK COLLECT • FETCH into nested table of type record: DECLARE TYPE DeptRecTab IS TABLE OF dept%ROWTYPE; dept_recs DeptRecTab; CURSOR c1 IS SELECT deptno, dname, loc FROM dept WHERE deptno > 10; BEGIN OPEN c1; FETCH c1 BULK COLLECT INTO dept_recs LIMIT 200; END; PL/SQL security • By default PL/SQL ignores roles, it only sees privileges granted directly • To access table from some other schema: – grant direct access to it, e.g. • GRANT select ON schema.name TO some_user; – define the procedure with invoker rights Invoker rights • To create procedure with invoker rights: CREATE OR REPLACE PROCEDURE test AUTHID CURRENT_USER IS BEGIN ... END; Invoker rights • AUTHID is specified in the header of a program unit. The same cannot be specified for individual programs or methods within a package or object type. • Definer rights will always be used to resolve any external references when compiling a new routine. • For an invoker rights routine referred in a view or a database trigger, the owner of these objects is always considered as the invoker, and not the user triggering it. Standard PL/SQL packages • • • • • • • • • DBMS_JOB – handles database jobs DBMS_LOB – handle BLOB and CLOB types DBMS_MVIEW – manage materialized views DBMS_OUTPUT – print messages to console DBMS_RANDOM – generate random numbers UTL_FILE – access files from PL/SQL programs UTL_HTTP – make HTTP requests UTL_SMTP – send email from PL/SQL UTL_TCP – make TCP/IP connections DBMS_JOB DBMS_JOB.SUBMIT( JOB OUT BINARY_INTEGER, WHAT IN VARCHAR2, NEXT_DATE IN DATE DEFAULT SYSDATE, INTERVAL IN VARCHAR2 DEFAULT 'NULL', NO_PARSE IN BOOLEAN DEFAULT FALSE, INSTANCE IN BINARY_INTEGER DEFAULT ANY_INSTANCE, FORCE IN BOOLEAN DEFAULT FALSE); DBMS_JOB.RUN( JOB IN BINARY_INTEGER, FORCE IN BOOLEAN DEFAULT FALSE); DBMS_LOB Functions: OPEN – open specified LOB READ – read values from LOB WRITE – write to LOB GETLENGTH – get current size of the LOB CLOSE – close LOB CREATETEMPORARY – create temporary LOB FREETEMPORARY – release temporary LOB SUBSTR – return part of the LOB DBMS_MVIEW • Package for managing materialized views • Main functions: • REFRESH – refreshes single materialized view • REFRESH_ALL_MVIEWS DBMS_OUTPUT • Writes output that can be viewed on the console • Useful for debugging PL/SQL code • Main functions: • PUT_LINE – write one line of text to console • NEWLINE – write end of line character • PUT – write text without end of line character • Note: there are limits on the size of the output buffer. Large texts may be truncated DBMS_RANDOM • Package for generating random numbers • Main functions: • INITIALIZE • TERMINATE • RANDOM – generate random number • Possible uses: SELECT * FROM tab1 ORDER BY DBMS_RANDOM.RANDOM; UTL_FILE • Functions for accessing files from the database • Special privileges are required to access files • Functions are similar to C stdio library: • FOPEN • FCLOSE • PUT • PUT_RAW • GET_LINE • GET_RAW Other packages • UTL_HTTP • Functions for accessing HTTP servers (also using SSL) • UTL_SMTP • Functions for sending email from PL/SQL (low level package) • UTL_TCP • Functions for connecting to servers using TCP/IP