===== CS 328 - Week 5 Lecture 2 - 2024-02-14 ===== ===== TODAY WE WILL ===== * announcements * on the data tier, continuing our intro to PL/SQL: * a selection of PL/SQL loops * intro to PL/SQL exception-handling ===== * Should be working on Homework 4! ===== REMINDER: ==== ****** ==== NEW EXAM 1 DATE - WEDNESDAY, FEBRUARY 28 <=========== ==== ****** ==== * Exam 1 review - moving to Wednesday, February 21 * zyBooks Chapters 1, 2 deadline - moving to Friday, February 23 * Exam 1 - moving to Wednesday, February 28 ===== note: while calling a PL/SQL function/procedure from SQL*Plus has special syntax, calling them from another PL/SQL subroutine should look familiar! ==== * remember job_overview? and job_count? COULD call them in another PL/SQL subroutine with just: begin ... local_var := job_count('President'); job_overview('President'); ... end; / show errors * (and we made a REFACTORED version of procedure job_overview in 328lect05-2.sql, now calling job_count) AND adding a NOTE in its opening comment that: uses: function job_count ...because you/someone in the future might need to know that!) ===== a selection of PL/SQL loops! ===== * TODAY: if I write something in [ ], it means that syntax is OPTIONAL AND: if I write { choice1 | choice2 }, it means you can choose choice1 or choice2 * loop statement implements iteration something LOOP statements; END LOOP; * the LOOP keyword can be on its own line, or at the end of the something line ===== * here's a basic while loop in PL/SQL: WHILE bool_expr LOOP statements; END LOOP; ===== * here's a "classic" iterative for oop in PL/SQL: FOR desired_index IN [REVERSE] start_num .. end_num LOOP statements; END LOOP; * desired_index is set to start_num, then proceeds for each value until end_num * those two periods .. *ARE* part of the statement's syntax!! * FUN FACT: you CANNOT change the desired_index loop control variable within the body of this loop! * FUN FACT: the variable you use for desired_index CAN be a local variable you have declared -- it can also be a NEW variable, which will be automatically declared; BUT in that case, as in C++, the scope of the automatically-declared variable is JUST the body of the loop! ==== RABBIT HOLE from AFTER CLASS ==== * YIKES -- found out after class that REVERSE is a little DIFFERENT than I expected! AS EXPECTED, * for i in 2 .. 5 loop dbms_output.put_line('i: ' || i); end loop; prints: i: 2 i: 3 i: 4 i: 5 SURPRISING (to me): * but REVERSE starts from the ***end_num*** and goes to the ***start_num*** -- (that is, you STILL put smaller value and then larger value, it just starts at the larger value) for j in REVERSE 2 .. 5 loop dbms_output.put_line('j: ' || j); end loop; prints: j: 5 j: 4 j: 3 j: 2 * and -- what if you put: for k in REVERSE 5 .. 2 loop dbms_output.put_line('k: ' || k); end loop; ...! it prints NOTHING; (no error, guess it just doesn't enter loop because 2 < 5?) * wait -- then what happens if you put: for m in 5 .. 2 loop dbms_output.put_line('m: ' || m); end loop; also prints nothing (no error, guess it just doesn't enter loop because 5 > 2...) SO: I *think* this is true: * if no REVERSE, starts desired_index at start_num and adds 1 to desired_index each time until it is greater than end_num; * if have REVERSE, starts desired_index at ***end_num*** and subtracts 1 from desired_index each time until it is less than ***start_num*** ==== END of after-class RABBIT HOLE ==== ===== * here's my favorite: a cursor-controlled for loop! for desired_row_name IN { cursor_name | select_stmt } loop statements; ... desired_row_name.desired_attrib_name .... end loop; * desired_row_name is set to EACH cursor row/projected select_stmt row in turn -- note that its type is one able to hold an entire ROW! * within the loop, you can get the value for a particular attribute within the current row with the syntax: desired_row_name.desired_attrib_name * for example: for next in (select * from dept) loop dbms_output.put_line('Name: ' || next.dept_name); end loop; ===== PL/SQL exception handling ===== * exception - a "bad thing" that happens at run time, that causes an error of some sort; PL/SQL, like many other languages, provides a way of HANDLING exceptions * PL/SQL handles these with an EXCEPTION block, which must be right before the END; of a block begin .... EXCEPTION WHEN desired_exc THEN statements; WHEN desired_exc THEN statements; .... WHEN OTHERS THEN statements; end; * if an exception is RAISED in a block, control passes immediately to the EXCEPTION part, and to the first WHEN whose type matches the type of of the raised exception. * That WHEN's statements are done, and that block is now exited. * how is an exception raised? it might be a predefined "bad" action, or you CAN raise an exception within code; ===== Examples of PL/SQL built-in exceptions ===== NO_DATA_FOUND CURSOR_ALREADY_OPEN TOO_MANY_ROWS INVALID_CURSOR ZERO_DIVIDE LOGIN_DENIED DUP_VALUE_ON_INDEX NOT_LOGGED_ON VALUE_ERROR PROGRAM_ERROR INVALID_NUMBER STORAGE_ERROR TIMEOUT_ON_RESOURCE TRANSACTION_BACKED_OUT ...and maybe more?? ===== * you CAN define your own exception, and raise it with a raise statement: create or replace ... past_due exception; ... begin .... if pd_yet = false then raise past_due; end if; ===== WEIRD PL/SQL thing discovered after class ...it's hard to print a BLANK line to the screen in PL/SQL! ===== * evidently it tries reeeeeallly hard to suppress blanks...?! * dbms_output *package* really is intended to debugging messages...! * what FINALLY worked: using another of DBMS_OUTPUT's procedures, PUT, with PL/SQL's way of indicating the ASCII character for a newline, chr(10): dbms_output.put(chr(10)); * you'll see this in 328lect05-2.sql, because I wanted to visually separate the output of the different parts...