===== CS 328 - Week 5 Lecture 2 - 2025-02-19 ===== ===== TODAY WE WILL: ===== * announcements * DATA TIER: continuing intro to PL/SQ: * a selection of PL/SQL loops * intro to PL/SQL exceptions * prep for next class ==== * https://csclubhumboldt.org/hackathon * to sign up for this year's CS Club Hackathon * sign up by March 6 ==== * should be working on Homework 4! * at-least-1st-attempts due by 11:59 pm on Friday, Feb 21 * and be sure to complete the zyBooks chapters 1 and 2 activities by 11:59 pm on Friday, February 28 to receive credit for those ===== several flavors of PL/SQL repetition! ===== * the LOOP statement implements repetition and, yes, it is terminated with: END LOOP; * BUT there is syntax that PRECEDES the LOOP keyword that determines what kind of loop it is; * we will cover 3 versions here -- there are more in the Oracle documentation, BUT note that I am deliberately skipping some UGLY loop forms; * so to be safe ASK ME before you use a loop form that's NOT one of these three... * CLASS STYLE: no GOTO or EXIT WHEN statements are allowed in CS 328 -- they can be a sign of bad logic structure, and when they aren't, exception-handling is probably a better choice ===== * classic while loop: ===== WHILE bool_expr LOOP desired_statememt; ... desired_statememt; END LOOP; * it works like I hope you expect * leading decision loop that initially checks bool_expr and only enters loop body if bool_expr is true * then, after completing body, sees if bool_expr still true and repeats body if so and exits if not * ( ) are NOT required around its bool_expr, but it is fine to put them if you'd like! * keyword LOOP can go at the end of the loop's first line, or on the second line: while bool_expr loop ... end loop; * might be easier not to forget the keywork LOOP this way, and "brackets" the END LOOP; nicely... ===== * an interesting variant of a count-controlled FOR loop: ===== FOR desired_index IN lowest_num .. highest_num LOOP desired_statement; ... desired_statement; END LOOP; * those two periods .. in lowest_num .. highest_num *ARE* part of the statement's syntax!! * sets desired_index to EACH value in lowest_num .. highest_num in turn, and inclusive * 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! ******* CORRECTION AFTER CLASS!!!!!!!! ********* * you can have an optional REVERSE -- FOR desired_index IN REVERSE lowest_num .. highest_num LOOP * BUT I was WRONG when I said you put the largest number first -- you SHOULD still put lowest_num .. highest_num...! * (but it DOES set desired_index to highest_num first, and progresses through lowest_num) ==== 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 ==== * see procedure reverse_play added to 328lect05-2.sql after class, demonstrating the above! ===== * and: a cursor-controlled for loop, that can loop through the results of a SQL SELECT statement! ===== FOR desired_row_name IN [(desired_select) | cursor_name] LOOP desired_statement; ... and you can refer to an attribute in the current row using desired_row_name.attrib_name ... desired_statement; END LOOP; * fun fact: if you put a SELECT statement in the cursor-controlled for loop first line, it MUST be in parentheses! * 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! * (it is automatically declared if you haven't previously declred it) * 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; * you can also declare a cursor in the declaration part -- for example, /*=== example of declaring a cursor ===*/ CURSOR sales_curs IS select empl_num, empl_last_name from empl where job_title = 'Sales'; * then, in that block's body, you could have: for next_sales_empl in sales_curs loop dbms_output.put_line(next_sales_empl.empl_num || ': ' || next_sales_empl.empl_last_name); end loop; ===== PL/SQL exception handling ===== * certain run-time bad things can happen at run-time, of course * 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 * EXCEPTIONS of various types might be RAISED, and you can write your PL/SQL to handle these; * you can declare your own exceptions, or you can use predefined exceptions * you put your exception-handling code within the EXCEPTION BLOCK of a PL/SQL block * must be right before the END; of a block BEGIN .... EXCEPTION WHEN desired_exc1 THEN statements; WHEN desired_exc2 THEN statements; ... WHEN OTHERS THEN statements; END; * if an exception is RAISED in a block, control passes immediately to the EXCEPTION block, 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 <-- !!!!! you don't do another WHEN's actions, you don't return to where the exception occurred! * 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 ...and several more! * see Table 12-3 at: https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/predefined-exceptions.html ===== * 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, in the reverse_play procedure added after class, because I wanted to visually separate the output of the different parts...