co•he•sion noun \kō-ˈhē-zhən\ 1 : the act or state of sticking together tightly; especially: unity Trigger Writing is Alive and Well Larry Holder The University of Tennessee at Martin Tuesday November 5, 1:30-2:30 pm Course ID: 193 Welcome! Come join me on an adventure in learning the basics of writing database triggers, including a look at several real-world examples that can enhance your support and functionality of Banner. 2 CoHEsion Summit What’s a Trigger? We ain’t talkin’ about Roy’s noble steed... Ok, so... what’s a trigger? • • • 5 A PL/SQL object, akin to a Procedure. Most often, a cool, calm & calculated reaction to an event, such as insert, update, delete, or even select, on a table. Sometimes, a reaction to a database event, such as a user logging onto the database. CoHEsion Summit What can I do with it? • • • • 6 Modify a field before it is inserted or updated. Insert, update, or delete data in other tables. Perform additional error-checking, and prevent an unwanted insert, update, or delete from occurring. Notify someone by email about an event. CoHEsion Summit What schema owns the custom triggers? • • 7 We chose to let SATURN own our custom triggers associated with SATURN-owned tables; likewise for GENERAL, WTAILOR, etc. Be sure the owner of the trigger is granted rights on any tables it references or modifies that are owned by another schema. CoHEsion Summit What naming convention is used? UTM_ST_SARADAP_PRE_IU_ROW • • • 8 A pre-insert / update trigger, at the row level, on the table SARADAP. “UTM” is our prefix, and “ST” is standard for “Student Trigger”. Remember 30-character object name max within Oracle. Add numeric suffix if a tie-breaker is needed (multiple triggers on same table are allowed). CoHEsion Summit Special variables :old.spraddr_street_line1 • meaningless for INSERT, will yield NULL :new.spraddr_street_line1 9 CoHEsion Summit Special variables (2) INSERTING, UPDATING, DELETING • Useful if trigger handles more than one mode, such as both INSERT and UPDATE, and your logic needs to know which occurred. IF INSERTING THEN do this; ELSE do that; END IF; 10 CoHEsion Summit Special variables (3) USER - the Oracle userid of who is connected • For self-service, keep in mind that the userid is likely something like WWW_USER. SYSDATE - the current date and time DATABASE_NAME • Differentiate between production and test: IF substr(DATABASE_NAME,1,4)= 'PROD' 11 CoHEsion Summit Examples of Before/After & Row/Statement create or replace trigger trigger_name BEFORE INSERT or UPDATE on table_name FOR EACH ROW create or replace trigger trigger_name AFTER DELETE on table_name Firing order: 1. Before Statement (once only) 2. Before Row (once per each affected row) 3. The actual DML statement 4. After Row (once per each affected row) 5. After Statement (once only) 12 statement level if no "for each row" CoHEsion Summit Example: Modifying fields "before" create or replace trigger utm_st_sarpers_pre_in_up_row BEFORE INSERT or UPDATE on SARPERS for each ROW BEGIN IF :new.sarpers_last_name IS NOT NULL THEN IF :new.sarpers_last_name = UPPER(:new.sarpers_last_name) OR :new.sarpers_last_name = LOWER(:new.sarpers_last_name) THEN :new.sarpers_prefix := INITCAP(:new.sarpers_prefix); :new.sarpers_first_name := INITCAP(:new.sarpers_first_name); :new.sarpers_middle_name1 := INITCAP(:new.sarpers_middle_name1); :new.sarpers_middle_name2 := INITCAP(:new.sarpers_middle_name2); :new.sarpers_last_name := INITCAP(:new.sarpers_last_name); :new.sarpers_suffix := INITCAP(:new.sarpers_suffix); :new.sarpers_nickname := INITCAP(:new.sarpers_nickname); :new.sarpers_combined_name := INITCAP(:new.sarpers_combined_name); :new.sarpers_former_name := INITCAP(:new.sarpers_former_name); END IF; END IF; END; / 13 CoHEsion Summit Example: Writing to an additional table create or replace trigger utm_st_sarctrl_post_ins_row AFTER INSERT on SARCTRL for each ROW DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN insert into saraatt values (:new.sarctrl_pidm, :new.sarctrl_term_code_entry, :new.sarctrl_appl_no_saradap, 'WAPP', sysdate); COMMIT; EXCEPTION WHEN OTHERS THEN NULL; COMMIT; END; / 14 Note the use, in this example, of autonomous transaction, if you wish or need to commit an action independently of the transaction causing the trigger. Otherwise, a trigger cannot include a commit. CoHEsion Summit Example: Additional error checking create or replace trigger utm_st_spriden_pre_insert_row BEFORE INSERT on SPRIDEN for each ROW BEGIN IF :new.spriden_change_ind IS NULL THEN IF (SUBSTR(:new.spriden_id,1,3) = '960' OR SUBSTR(:new.spriden_id,1,1) = '-') OR (SUBSTR(:new.spriden_id,1,1) > '9' AND :new.spriden_entity_ind = 'C') OR (LENGTH(:new.spriden_id) <= 7 AND :new.spriden_entity_ind = 'C') THEN NULL; ELSE RAISE_APPLICATION_ERROR (-20501, 'UTM ERROR *** ADDING THIS ID VALUE IS BLOCKED'); END IF; END IF; END; / 15 CoHEsion Summit Example: Sending an email notification create or replace trigger utm_st_sortest_post_del_row after DELETE on SORTEST for each ROW w_conn w_crlf w_mailhost w_mesg w_from w_to w_test_email w_stu_id w_stu_name UTL_SMTP.connection; varchar2(2) := CHR(13) || CHR(10); varchar2(30) := 'your_email_server'; varchar2(4000); varchar2(30) := 'your_from_address'; varchar2(30) := 'your_recipient_address'; varchar2(30) := 'your_reciepient_for_non_production'; varchar2(9); varchar2(100); BEGIN BEGIN IF SUBSTR(DATABASE_NAME,1,4) != 'PROD' THEN w_to := w_test_email; END IF; select into from where and 16 spriden_id, spriden_first_name || ' ' || spriden_last_name w_stu_id, w_stu_name spriden spriden_pidm = :old.sortest_pidm spriden_change_ind is null; continued... CoHEsion Summit Example: Sending an email... (continued) w_mesg := 'Date: ' || TO_CHAR(SYSDATE,'DD Mon YY HH24:MI:SS') || w_crlf || 'From: ' || w_from || w_crlf || 'Subject: ' || 'Deletion from SORTEST (SOATEST)' || w_crlf || 'To: ' || w_to || w_crlf || w_crlf || user || ' deleted an entry for ' || w_stu_name || ' (' || w_stu_id || ') of' || ': code = ' || :old.sortest_tesc_code || ', date = ' || to_char(:old.sortest_test_date,'MM-DD-YYYY') || ', score = ' || :old.sortest_test_score || w_crlf || w_crlf || ' ' || w_crlf; w_conn := UTL_SMTP.open_connection(w_mailhost, 25); UTL_SMTP.helo(w_conn, w_mailhost); UTL_SMTP.mail(w_conn, w_from); UTL_SMTP.rcpt(w_conn, w_to); UTL_SMTP.data(w_conn, w_mesg); UTL_SMTP.quit(w_conn); EXCEPTION WHEN OTHERS THEN NULL; END; END; / 17 CoHEsion Summit Disabling and enabling a trigger alter trigger [owner.]name DISABLE; alter trigger [owner.]name ENABLE; • Automatically enabled when initially created. select * from dba_triggers where trigger_name like 'UTM%'; 18 CoHEsion Summit Mutating trigger example create or replace trigger utm_junk1 BEFORE insert or update of spbpers_ssn on SPBPERS for each ROW DECLARE w_pidm spbpers.spbpers_pidm%type; w_new_ssn spbpers.spbpers_ssn%type; w_flag char(1) := NULL; CURSOR select from where and check_for_dup IS distinct 'Y' spbpers spbpers_ssn = w_new_ssn spbpers_pidm != w_pidm; BEGIN w_pidm w_new_ssn := :new.spbpers_pidm; := :new.spbpers_ssn; IF :new.spbpers_ssn IS NOT NULL THEN OPEN check_for_dup; FETCH check_for_dup INTO w_flag; CLOSE check_for_dup; IF w_flag = 'Y' THEN RAISE_APPLICATION_ERROR(-20501, '*** SSN already in use ***'); END IF; END IF; END utm_junk1; / 19 CoHEsion Summit Results (mutation) ... ORA-04091: table SATURN.SPBPERS is mutating, trigger/function may not see it ORA-06512: at "SATURN.UTM_JUNK1", line xx ORA-06512: at "SATURN.UTM_JUNK1", line xx ORA-04088: error during execution of trigger 'SATURN.UTM_JUNK1' 20 CoHEsion Summit Solution, part 1 of 3 (package) create or replace package utm_st_spbpers_pkg as TYPE t_pidms IS TABLE spbpers.spbpers_pidm%TYPE TYPE t_new_ssns IS TABLE spbpers.spbpers_ssn%TYPE OF INDEX BY BINARY_INTEGER; OF INDEX BY BINARY_INTEGER; pidms new_ssns t_pidms; t_new_ssns; num_entries BINARY_INTEGER := 0; END utm_st_spbpers_pkg; / 21 CoHEsion Summit Solution, part 2 of 3 (before / row) create or replace trigger utm_st_spbpers_pre_iu_row BEFORE insert or update of spbpers_ssn on SPBPERS for each ROW BEGIN utm_st_spbpers_pkg.num_entries := utm_st_spbpers_pkg.num_entries + 1; utm_st_spbpers_pkg.pidms (utm_st_spbpers_pkg.num_entries) := :new.spbpers_pidm; utm_st_spbpers_pkg.new_ssns (utm_st_spbpers_pkg.num_entries) := :new.spbpers_ssn; END utm_st_spbpers_pre_iu_row; 22 CoHEsion Summit Solution, part 3 of 3 (after / statement) create or replace trigger utm_st_spbpers_post_iu_stmt AFTER insert or update of spbpers_ssn on SPBPERS DECLARE w_num_entries w_pidm w_new_ssn w_flag w_error CURSOR select from where and 23 utm_st_spbpers_pkg.num_entries%type; spbpers.spbpers_pidm%type; spbpers.spbpers_ssn%type; char(1) := NULL; char(1) := 'N'; check_for_dup IS distinct 'Y' spbpers spbpers_ssn = w_new_ssn spbpers_pidm != w_pidm; continued... CoHEsion Summit BEGIN w_num_entries := utm_st_spbpers_pkg.num_entries; FOR z_index IN 1..w_num_entries LOOP w_pidm w_new_ssn := utm_st_spbpers_pkg.pidms (z_index); := utm_st_spbpers_pkg.new_ssns (z_index); IF w_new_ssn IS NOT NULL THEN OPEN check_for_dup; FETCH check_for_dup INTO w_flag; CLOSE check_for_dup; IF w_flag = 'Y' THEN w_error := 'Y'; END IF; continued... END IF; END LOOP; 24 CoHEsion Summit IF w_error = 'Y' THEN utm_st_spbpers_pkg.num_entries := 0; RAISE_APPLICATION_ERROR (-20501, '*** SSN ALREADY IN USE ***'); END IF; utm_st_spbpers_pkg.num_entries := 0; END utm_st_spbpers_post_iu_stmt; / 25 CoHEsion Summit New Results (no mutation) ... ORA-20501: *** SSN ALREADY IN USE *** This is the application error that we intentionally raised ORA-06512: at "SATURN.UTM_SPBPERS_POST_IU_STMT", line xx ORA-04088: error during execution of trigger 'SATURN.UTM_SPBPERS_POST_IU_STMT' This is expected 26 CoHEsion Summit Another way – since 11g • • 27 “Compound” Trigger introduced in 11g Combines the 3 parts (package, before row, and after statement) • See Oracle 11g trigger documentation • Thanks to Rob Pierce CoHEsion Summit Compound Trigger CREATE OR REPLACE TRIGGER MYTRIGGER FOR UPDATE OF MYTABLE COMPOUND TRIGGER … (declarations) … BEFORE STATEMENT IS … END BEFORE STATEMENT; AFTER EACH ROW IS … END AFTER EACH ROW; END MYTRIGGER; 28 CoHEsion Summit By the way… The API logic introduced in Banner 7 changed a few things... for example, the column SGBSTDN_LEVL_CODE is still NULL when initially inserted; a subsequent Update populates it. I changed several POSTINSERT triggers to POST-INSERT/UPDATE and added the following logic at the beginning, to handle the initial setting of the column regardless of whether done by Insert or Update... IF (INSERTING AND :new.sgbstdn_levl_code IS NULL) OR (UPDATING AND :old.sgbstdn_levl_code IS NOT NULL) THEN GOTO skip_everything; END IF; ... <<skip_everything>> NULL; 29 CoHEsion Summit Helpful Debugging Tip Consider creating a table to hold debug info, which you can write to from any trigger that you are testing... to show you values in :old and :new, for example, plus anything else you care to review. I used this to debug the Post-Insert triggers mentioned on the previous slide... Create Table Trigger_Debug (trigger_name varchar2(30) not null, datetime date not null, codepoint varchar2(30) not null, info1 varchar2(100), info2 varchar2(100), info3 varchar2(100); Create Public Synonym Trigger_Debug for Trigger_Debug; Grant All on Trigger_Debug to Public; w_debug char(1) := 'Y'; turn this on or off as needed IF w_debug = 'Y' THEN INSERT INTO TRIGGER_DEBUG VALUES ('MY_TRIGGER',SYSDATE,'BEGIN', :OLD.SGBSTDN_LEVL_CODE, :NEW.SGBSTDN_LEVL_CODE, NULL); 30 CoHEsion Summit Summary Triggers are a great way to extend the functionality of Banner without introducing “baseline modifications”. Rome wasn’t built in a day. Start out simply, and add more at your own pace. 31 CoHEsion Summit Questions & Answers Ok, it’s your turn... 32 CoHEsion Summit Thank You! Larry Holder lholder@utm.edu www.utm.edu/staff/lholder Please complete the session evaluation form Course ID 193 General presentation Copyright © 2006-2013 Larry Holder (of The University of Tennessee at Martin). Photo of Roy Rogers and Trigger used by permission of the Roy Rogers-Dale Evans Museum, Branson, MO. No animals were harmed in the production of this presentation. No artificial ingredients added. Mileage may vary. All disclaimers apply. 33 CoHEsion Summit