Table of Contents

Page 511

Appendix A
Answers

Page 512

This appendix provides the answers to the quiz and exercise sections at the end of each chapter.

Day 1

Quiz

  1. What tells SQL*Plus to send your PL/SQL code to the Oracle database for execution?

    A Typing the slash character (/) on a line by itself immediately following the end of the PL/SQL block.

  2. What is the fundamental basis of all PL/SQL code?

    A The PL/SQL block.

  3. List an advantage of pushing program logic up to the server level.

    A There are at least two advantages:

  4. Name three Oracle products that use PL/SQL.

    A There are several that execute PL/SQL:

    And several that recognize it:
  5. What command tells SQL*Plus to display PL/SQL output?

    A SET SERVEROUTPUT ON

  6. Name at least two options for managing your PL/SQL source code.

    A The three options described in the chapter are as follows:

Page 513

Exercises

  1. If you didn't encounter any errors when compiling your first function, try putting some in on purpose. Then try out the SHOW ERRORS command.

    A I'll leave the task of generating errors up to you.

  2. Try each of the three ways mentioned in the chapter for managing your source code. Become familiar with the SQL*Plus EDIT command. Try using the @ command or the START command to execute your PL/SQL code from a text file.

    A You should be able to type any PL/SQL block into SQL*Plus, execute it, and then use the EDIT command to bring up Notepad so that you can change it.

    As for executing a PL/SQL block from a file, one possible solution is to create a file with these lines and call it test.sql:

    SET SERVEROUTPUT ON
    BEGIN
      dbms_output.put_line(`Hello there!');
    END;
    /
    

    You can then execute the file from SQL*Plus using the command

    @test
    

Day 2

Quiz

  1. Name several PL/SQL development tools.

    A SQL*Plus, Developer/2000 Forms, Developer/2000 Reports, Developer/2000 Graphs, and Oracle Call Interfaces.

  2. What is a bind variable?

    A A bind variable is a variable that is created outside PL/SQL but referenced within a PL/SQL block.

  3. What is a substitution variable?

    A A substitution variable is a user-defined variable. This variable is preceded by an ampersand (&) when it is defined.

Exercise

Write a simple piece of code outline that uses substitution variables and bind variables.

A The following is a simple illustration (code outline) of using bind variables and substitution variables in the same PL/SQL block:

DECLARE

v_Emp_name  varchar2(35);
v_emp_no    number(3);

Page 514

Day 3

Quiz

  1. What are three benefits of using functions and procedures?

    A Procedures and functions hide complexity, promote modularity, and allow for reuse of code.

  2. What values can a variable declared as NUMBER(6,2) hold? What will be the maximum value?

    A A declaration of NUMBER(6,2) allows you to store values such as 1234.56, _2333.99, and so on. The maximum value you can store is 9999.99.

  3. What values can a variable declared as NUMBER(2,2) hold? Where will rounding occur?

    A A declaration of NUMBER(2,2) allows you to store values such as 0.01, 0.02, up through 0.99. All values will be rounded to the nearest hundredth.

  4. What is the maximum length of a VARCHAR2 variable in PL/SQL? In the Oracle database?

    A The maximum length of a VARCHAR2 in PL/SQL is 32767 bytes. In an Oracle database, the maximum length is 2000 bytes.

  5. What can you do to ignore the time portion of a DATE variable?

    A The TRUNC() function can be used to truncate a date so that it no longer contains a time element.

  6. When comparing a VARCHAR2 and a CHAR variable, how can you eliminate any trailing spaces?

    A The RTRIM() function can be used to eliminate trailing spaces from both CHAR and VARCHAR2 strings.

Exercises

  1. Try writing an anonymous block that declares a variable and displays its value. Then add a nested block that declares a variable of the same name and displays its value. What happens and why?

Page 515

A Here is one solution:

--Remember to execute:SET SERVEROUTPUT ON
DECLARE
  state_name  VARCHAR2(30);

BEGIN
  state_name := `Michigan';

  --Now code a nested block which declares and prints
  --a variable of the same name.
  DECLARE
    state_name  VARCHAR2(30);
  begin
    state_name := `Tennessee';
    DBMS_OUTPUT.PUT_LINE(state_name);
  END;

  --Now print the state_name variable's value in the outer block.
  DBMS_OUTPUT.PUT_LINE (state_name);
END;
/

Tennessee
Michigan

PL/SQL procedure successfully completed.

The inner block will print the value assigned to state_name within that block. However, the value of state_name in the outer block is undisturbed because the scope of the inner block's state_name declaration is limited to that block.

  1. Write a function that computes a person's age in years. Hint: To get started on this, look at Listing 3.3.

    A Here is a PL/SQL block containing a function named age_as_of, which is one possible solution to the exercise of coding an age calculation function:

    --Remember to execute: SET SERVEROUTPUT ON
    DECLARE
      age   BINARY_INTEGER;
    
      birth_date    DATE := TO_DATE('11-15-1961','mm-dd-yyyy');
      current_date  DATE;
    
      FUNCTION age_as_of (birth_date IN DATE, as_of_date IN DATE)
      RETURN POSITIVE IS
        as_of_year  NATURAL;    --a year of 00 is valid.
        as_of_month POSITIVE;
        as_of_day   POSITIVE;
    
        birth_year    NATURAL;  --a year of 00 is valid.
        birth_month   POSITIVE;
        birth_day     POSITIVE;
    
        age   POSITIVE;
    
    
    

Page 516

  BEGIN
    --Get the various parts of the dates needed to determine age.
    as_of_year := TO_NUMBER(TO_CHAR(as_of_date,'yyyy'));
    as_of_month := TO_NUMBER(TO_CHAR(as_of_date,'mm'));
    as_of_day := TO_NUMBER(TO_CHAR(as_of_date,'dd'));

    birth_year := TO_NUMBER(TO_CHAR(birth_date,'yyyy'));
    birth_month := TO_NUMBER(TO_CHAR(birth_date,'mm'));
    birth_day := TO_NUMBER(TO_CHAR(birth_date,'dd'));

    --Now make the actual computation.
    IF as_of_month > birth_month THEN
      age := as_of_year - birth_year;
    ELSIF (as_of_month = birth_month) and (as_of_day >= birth_day) THEN
      age := as_of_year - birth_year;
    ELSE
      age := as_of_year - birth_year - 1;
    END IF;

    RETURN age;
  END;
BEGIN
  --Let's test each of the cases that the age
  --function needs to consider.
  DBMS_OUTPUT.PUT_LINE(`Age as of 11-13-1997');
  current_date := TO_DATE('11-13-1997','mm-dd-yyyy');
  age := age_as_of (birth_date, current_date);
  DBMS_OUTPUT.PUT_LINE(age);

  DBMS_OUTPUT.PUT_LINE(`Age as of 11-15-1997');
  current_date := to_date('11-15-1997','mm-dd-yyyy');
  age := age_as_of (birth_date, current_date);
  DBMS_OUTPUT.PUT_LINE(age);

  DBMS_OUTPUT.PUT_LINE(`Age as of 12-13-1997');
  current_date := to_date('12-13-1997','mm-dd-yyyy');
  age := age_as_of (birth_date, current_date);
  DBMS_OUTPUT.PUT_LINE(age);

  DBMS_OUTPUT.PUT_LINE(`Age as of 5-13-1997');
  current_date := TO_DATE(`5-13-1997','mm-dd-yyyy');
  age := age_as_of (birth_date, current_date);
  DBMS_OUTPUT.PUT_LINE(age);

END;
/
Age as of 11-13-1997
35
Age as of 11-15-1997
36
Age as of 12-13-1997
36
Age as of 5-13-1997
35

PL/SQL procedure successfully completed.

Page 517

Day 4

Quiz

  1. What is the difference between a unary operator and a binary operator?

    A A unary operator works on only one value. An example is the negation operator, which is often used to write negative numbers. Binary operators work on two values. Examples are the addition and multiplication operators.

  2. What are the results of each of the following expressions?

    a. (5-4)-(3-1)

    b. 4*2**3-2

    c. 4*2**(3-2)

    d. 4=4 AND 5=6 OR 3=3

    A The expressions evaluate as follows:

    a. (5-4)-(3-1) evaluates to -1.

    b. 4*2**3-2 evaluates to 30.

    c. 4*2**(3-2) evaluates to 8.

    d. 4=4 AND 5=6 OR 3=3 evaluates to true.

  3. Using the NOT operator, write equivalent expressions for each of the following:

    a. A <> B

    b. A < B

    c. (A <= B) AND (B <= C)

    A a. NOT (A = B)

    b. NOT (A >= B)

    c. NOT ((B < A) OR (B > C))

  4. Match the patterns and strings shown following. Hint: Not every string or pattern has a match, and one pattern matches more than one string.

    `123-45-6789''___-__-____'

    `Boom''John%'

    `Johnson''_oo_'

    `517-555-1212'

    `Broom'

    `Jonson'

    `Johnston'

    A `John%' matches `Johnson' and `Johnston'. `_oo_' matches `Boom'. `___-__-____' matches `123-45-6789'. The remaining strings and patterns don't match at all.

Page 518

  1. When does PL/SQL not pad strings with spaces, in order to make them of equal length, when doing comparisons?

    A Any time a VARCHAR2 string is involved.

Exercise

Write a function to compute wages based on an hourly rate and the number of hours worked. Have it use a minimum wage of $5 per hour if the rate is unknown. Have it also use the minimum wage if the rate is too low. A Here is one solution:

--Remember to execute: SET SERVEROUTPUT ON
DECLARE
FUNCTION wage_calculate (
  hours_worked IN NUMBER,
  hourly_rate IN NUMBER) RETURN NUMBER IS
hourly_rate_to_use  NUMBER;
minimum_wage  NUMBER := 5;
BEGIN
  IF (hourly_rate IS NULL) OR (hourly_rate < minimum_wage) THEN
    hourly_rate_to_use := minimum_wage;
  ELSE
    hourly_rate_to_use := hourly_rate;
  END IF;
  RETURN hours_worked * hourly_rate_to_use;
END;
BEGIN
  DBMS_OUTPUT.PUT_LINE(wage_calculate(40,10));
  DBMS_OUTPUT.PUT_LINE(wage_calculate(40,2));
  DBMS_OUTPUT.PUT_LINE(wage_calculate(40,NULL));
END;
/
400
200
200
PL/SQL procedure successfully completed.

Day 5

Quiz

  1. What parts of the function are required for coding?

    A The required parts of the function are the function keyword and name, the RETURN statement and type, and a BEGIN and an END statement with the function name occurring at the end of the END statement.

  2. If a function takes parameters, is it always necessary to pass these parameters from the calling statement?

Page 519

    A It is not always necessary to pass values, even if the function allows for this. To compensate, make sure that you have a DEFAULT for the parameter if nothing is passed.

  1. If an error occurs and you haven't coded an EXCEPTION statement, what is returned from the function?

    A The function will error out with no value returned.

  2. Is there a way to return more than one value from a function?

    A You can return more than one parameter by using the optional MODE of OUT or IN OUT. However, this is not a recommended programming practice.

  3. If you code an IF...ELSE statement, and you do not have any conditions to execute if the statement is false, how would you code the ELSE statement?

    A After the ELSE statement, code a NULL;.

  4. What are some of the common pitfalls in coding IF statements?

    A Some common errors are forgetting to put a space between END and IF; forgetting the semicolon after the END IF; missing an END IF statement, especially if nested IFs are used; and finally, misspelling ELSIF as ELSEIF.

  5. How can I determine what is wrong with my code when it compiles?

    A From your command prompt, type the words SHOW ERRORS and then troubleshoot. Remember, a program can compile and still be incorrect due to logic errors.

  6. When coding a loop in reverse, how must the beginning and ending values be coded?

    A Even though you are adding the REVERSE keyword, the starting and ending values must still be coded from lowest value to highest value.

Exercises

  1. Rewrite the Grade example from Listing 5.11 as a stored function that passes the parameter of the score and returns a value of a grade letter.

    A Here's one solution:

    CREATE OR REPLACE FUNCTION mygrade(p_score NUMBER)
         RETURN CHAR IS
    BEGIN
    IF p_Score >= 90 THEN
         RETURN `A';
    ELSIF p_Score >= 80 THEN
         RETURN `B';
    ELSIF p_Score >= 70 THEN
         RETURN `C';
    ELSIF p_Score >= 60 THEN
         RETURN `D';
    ELSE
         RETURN `E';
    
    

Page 520

    END IF;
    END;
    
  1. Rewrite the Grade example from Listing 5.11 and use between for the ranges. Make sure that there is no overlapping of ranges.

    A Here's one solution:

    DECLARE
    v_Score Number := 85; --Percentage
    v_LetterGrade Char(1);
    BEGIN
    IF v_Score between 90 and 100 THEN
         v_LetterGrade := `A';
    ELSIF v_Score between 80 and 89 THEN
         v_LetterGrade := `B';
    ELSIF v_Score between 70 and 79 THEN
         v_LetterGrade := `C';
    ELSIF v_Score between 60 and 69 THEN
         v_LetterGrade := `D';
    ELSE
         v_LetterGrade := `E';
    END IF;
         DBMS_OUTPUT.PUT_LINE(`Your Letter Grade is: ` || v_LetterGrade);
    END;
    /
    
  2. Write a loop that increments by a value of 3 and then multiplies a counter by the returned value of the function mypi. The range should be from 1 to 9. Output the values with DBMS_OUTPUT. Make sure that you have entered SET SERVEROUTPUT ON to see the output.

    A Here's one solution:

    BEGIN
         FOR v_loopcounter IN 1..9 LOOP
              IF MOD(v_loopcounter,3) = 0 THEN
                   DBMS_OUTPUT.PUT_LINE(`The counter * pi is ` ||
                        v_loopcounter * mypi );
              END IF; -- End execution of statements for even counter
         END LOOP;
    END;
    
  3. Write a loop to calculate a factorial. For example, 6! is 6 * 5 * 4 * 3 * 2 * 1.

    Allow the high boundary to be a variable that can change. Use an initial value of

    3 for testing.

    A Here's one solution:

    DECLARE
         v_factorial NUMBER := 1;
    BEGIN
         FOR v_loopcounter IN REVERSE 1..4 LOOP
          v_factorial := v_factorial * v_loopcounter;
              DBMS_OUTPUT.PUT_LINE(`Your factorial value is now ` ||
                   v_factorial);
         END LOOP;
    END;
    

Page 521

Day 6

Quiz

  1. True or False: The label name must be within the same PL/SQL block of code as the GOTO statement calling the label name.

    A True. This is one of many reasons why you probably will never need to use the GOTO statement.

  2. When should GOTO be used?

    A The GOTO statement is typically used in emergency situations that require you to respond immediately. It could be something as simple as a server going down, or as complex as an incoming nuclear warhead. Other than that, it is not good coding practice to use the GOTO statement.

  3. WHILE loops must end with a(n) ____________ statement.
    A END LOOP;
  4. Can you potentially write a WHILE loop that never ends?

    A Yes. As long as the condition never evaluates to true, the same code will repeat over and over again.

  5. What statement(s) allow you to abort the processing of a loop?

    A The EXIT and EXIT WHEN statements allow you to break out of the execution of a loop.

  6. In order to change execution of nested loops, you can use the EXIT and EXIT WHEN statement in conjunction with ____________.

    A Label names for loops

  7. Must you have EXIT or EXIT WHEN as part of a simple LOOP?

    A This is not required as part of the syntax. However, you should make it mandatory as part of good programming practice instead of having infinite loops!

  8. Does Oracle have a REPEAT...UNTIL loop?

    A No. You can simulate this by using a simple LOOP with the EXIT or EXIT WHEN statement.

  9. In a simple LOOP, where is the best location for the EXIT or EXIT WHEN statements to appear?

    A These statements should appear at the beginning or at the end of the LOOP body to avoid potential logic errors.

Exercises

  1. Create an example using GOTO that checks some variable for a value of 10 and then branches off to a NULL statement.

Page 522

    A Here is one solution:

    DECLARE
         v_GOTOVARIABLE NUMBER := 0;
    BEGIN
         v_GOTOVARIABLE := 10;
         IF v_GOTOVARIABLE = 10 THEN
             GOTO nullstatement;
         ELSE
              NULL;
         END IF;
    <<nullstatement>>
         NULL;
    END;
    /
    
  1. Create a WHILE loop to calculate a factorial. For example, 6! is 6 * 5 * 4 * 3 * 2 * 1. Use an initial value of 4! for testing. Make sure to issue the command SET SERVEROUTPUT ON and use DBMS_OUTPUT.

    A Here is one solution:

    DECLARE
         v_factorial NUMBER := 1;
         v_factorial_counter NUMBER := 4;
    BEGIN
         WHILE v_factorial_counter != 1 LOOP
              v_factorial := v_factorial * v_factorial_counter;
              DBMS_OUTPUT.PUT_LINE(`Your factorial value is now `
                   || v_factorial);
              v_factorial_counter := v_factorial_counter - 1;
         END LOOP;
    END;
    /
    
  2. Create the same factorial calculation as Exercise 2, but use a simple LOOP statement instead.

    A Here is one solution:

    DECLARE
         v_factorial NUMBER := 1;
         v_factorial_counter NUMBER := 4;
    BEGIN
         LOOP
              v_factorial := v_factorial * v_factorial_counter;
              DBMS_OUTPUT.PUT_LINE(`Your factorial value is now `
                   || v_factorial);
              v_factorial_counter := v_factorial_counter - 1;
              EXIT WHEN v_factorial_counter = 1;
         END LOOP;
    END;
    /
    

Page 523

Day 7

Quiz

  1. True or False: All functions are accessible from within PL/SQL.

    A False. There are many SQL-only commands, which perform calculations on rows, such as AVG, MIN, MAX, and so on.

  2. What function would I use to combine two strings together?

    A You would use the CONCAT function; however, you can still fall back on || to concatenate strings.

  3. What function would convert '11/28/97' to an Oracle DATE?

    A The TO_DATE function gives you this flexibility.

  4. In a VARCHAR2 string, each string can be a variable length. What function would you use to determine the length so that you can search through the entire string?

    A By using the LENGTH function, you can determine the actual length of VARCHAR2. If the value is NULL, NULL is returned. If you are using type CHAR, it will include the padded spaces in the count.

  5. How do you get rid of padded spaces to the right of a string in Oracle?

    A By using RTRIM and specifying the space as a character, you can trim any padded spaces in a string.

  6. To determine the remainder, you would use the _____________ function.

    A MOD

  7. To determine how many months a customer is delinquent, you can use the _________ function.

    A MONTHS_BETWEEN

  8. The TRUNC and ROUND functions can be used with what datatypes?

    A Both NUMBER and DATE include the ROUND and TRUNC functions.

Exercises

  1. Create a PL/SQL block that reads in the month of a date and displays the month in a Roman numeral format. Use a date of 06/11/67. This will allow you to practice the TO_CHAR function. When printing the Roman numeral equivalent, use LTRIM to remove spaces padded to the left of the Roman numeral. If you are really ambitious, on your own you could create the same RM type function by using IF...THEN...ELSE statements for practice from Day 5, "Using Functions, IF Statements, and Loops." Remember, practice helps to solidify your knowledge through repetition and understanding.

Page 524

    A Here is one solution:

    DECLARE
         v_Hold_Month Number;
    BEGIN
         v_Hold_Month := TO_NUMBER(TO_CHAR(TO_DATE('11-JUN-67'),'MM'));
         DBMS_OUTPUT.PUT_LINE(v_Hold_Month);
         DBMS_OUTPUT.PUT_LINE(`Converted to Roman Numeral ` ||
              LTRIM(TO_CHAR(v_Hold_Month,'RM'),' `));
    END;
    /
    
    6
    Converted to Roman Numeral VI
    
  1. Use the TRUNC function on the SYSDATE to round to the nearest century.

    A The answer is

    SELECT TO_CHAR(TRUNC(SYSDATE,'CC'),'MM/DD/YYYY HH:MI:SS AM')
         "Today's Date and Time"
     from DUAL
    
    Today's Date and Time
    -------------------------
    01/01/1900 12:00:00 AM
    
  2. Use CONCAT to link two strings together. Repeat the same line by using || instead of CONCAT.

    A Here is one solution:

    DECLARE
         v_String1 VARCHAR2(60) := CONCAT(`Connect String1 to',
                                          ` String2');
         v_String2 VARCHAR2(60) := `Connect String1 to' || ` String2';
    BEGIN
         DBMS_OUTPUT.PUT_LINE(v_String1);
         DBMS_OUTPUT.PUT_LINE(v_String2);
    END;
    /
    
    Connect String1 to String2
    Connect String1 to String2
    
  3. Calculate the number of days between 01/01/97 to 03/31/97. Remember to use the TRUNC function to eliminate the TIME dependency.

    A The answer is

    SELECT TRUNC(TO_DATE('03/31/97','MM/DD/YY')) -
         TRUNC(TO_DATE('01/01/97','MM/DD/YY')) "Days_Subtracted"
         from DUAL;
    

Page 525

    Your output will be
    Days_Subtracted
    ---------------
                 89
    
  1. Convert the CHARACTER string '06/11/67' to a date, and subtract from 06/11/97 to see how old your author is (and holding).

    A The answer is

    SELECT (TO_DATE('06/11/97','MM/DD/YY') -
         TO_DATE('06/11/67','MM/DD/YY'))/365 "Years Old"
         from DUAL;
    
    Years Old
    ---------
    30.021918
    
  2. Calculate how many months are between 05/15/97 to 08/22/97.

    A The answer is

    SELECT MONTHS_BETWEEN('22-AUG-97','15-MAY-97') "Fractional"
         from DUAL;
    
    Fractional
    ----------
     3.2258065
    
  3. Round the SYSDATE to the nearest century.

    A The answer is

    SELECT TO_CHAR(ROUND(SYSDATE,'CC'),'MM/DD/YYYY HH:MI:SS AM')
        "Today's Date and Time"
     from DUAL;
    
    Today's Date and Time
    -------------------------
    01/01/2000 12:00:00 AM
    
  4. Calculate the time in Newfoundland from Central Standard Time from 02/22/97, 05:00 a.m.

    A Here is one solution:

    SELECT TO_CHAR(NEW_TIME(TO_DATE('02-22-97 05:00:00 AM',
              `MM-DD-YY HH:MI:SS AM'),
              `CST','NST'), `DD-MON-YY HH:MI:SS AM')
                   "Central to Newfoundland"
         from DUAL;
    

Page 526

    Your output will be
    Central to Newfoundland
    ------------------------
    22-FEB-97 07:30:00 AM
    
  1. From Listing 7.22, subtract one month and explain the answer.

    A Several possible answers are

    SELECT ADD_MONTHS(TO_DATE('31-MAR-97'),-1) from DUAL;
    SELECT ADD_MONTHS(TO_DATE('31-MAR-97'),-1.5) from DUAL;
    SELECT ADD_MONTHS(-1,TO_DATE('31-MAR-97')) from DUAL;
    
    The output, of course, is the end of February because February has fewer than

    30 days:

    ADD_MONTH
    ---------
    28-FEB-97
    
  2. Calculate the number of days until Christmas from the last day of the month of today's date! (We don't get paid until the end of the month!)

    A Here is one solution:

    SELECT LAST_DAY(SYSDATE) "Last_Day",
              TO_DATE('25-DEC-97') - LAST_DAY(SYSDATE) "Shopping Days"
         from DUAL;
    
    Last_Day  Shopping Days
    --------- -------------
    30-JUN-97     177.67266
    

Day 8

Quiz

  1. What statement is used to recompile a procedure?

    A The CREATE OR REPLACE PROCEDURE command is used to recompile a procedure.

  2. How do you invoke a procedure?
    A You use the execute command if you want to explicitly and manually call a procedure. From within a package or other PL/SQL construct, you simply list the procedure name in the code, and the call to it is made automatically.
  3. Name at least four predefined Oracle exception errors.

    A There are many Oracle predefined exceptions including: no_data_found, too_many_rows, invalid_cursor, value_error, invalid_number, zero_divide, cursor_already_open, login_denied, and others.

  4. How do you call a module of a package?

Page 527

A To call a specific procedure within a package, you use dot notation as shown in the following example:

package_name.procedure_name

Exercises

  1. Write a package specification for the functions written in previous chapters. Additionally, include in the specification one or two of the procedures used in this chapter.

    A Package specifications contain public declarations of the name of the package and its functions and procedures. The following is an example and might differ slightly from your answer:

    CREATE PACKAGE day_8_package_spec as
    -- package name declaration
    FUNCTION  inv_count (qty number, part_nbr varchar2(25))
    -- function declaration
    return number;
    PROCEDURE pay_salary (emp_id number);
    -- procedure declaration
    PROCEDURE hire_employee (emp_name, pay_date number, pay_type char));
    --procedure declaration
    END day_8_package_spec;
    
  2. Write an exception-handling piece of code to trap the error of receiving more rows than you expected as well as an unknown error.

    A One possible way to write this exception handler is

    exception
    WHEN too_many_rows THEN
      ...          -- code to be executed when a SELECT returns
                   -- too many rows
    END;
    WHEN others THEN
    ...            -- code to be executed when an exception is
                   -- encountered which is not the too_many_rows
    END;
    

Day 9

Quiz

  1. Name some of the database objects that you can base a variable declaration on.
    A PL/SQL variables can be based on database table columns, other variables, constants, and cursors.
  2. What is the variable attribute you use to base a variable on a table column?
    A The %type variable attribute enables you to base a variable on a specific database table column.

Page 528

  1. Name several of the PL/SQL table built-in functions.

    A The following built-ins can be used with the PL/SQL table: DELETE, first, last, next, prior, and count.

  2. What are the four SQL DML statements permitted in a PL/SQL block?

    A The four DML statements that are supported within a PL/SQL block are INSERT, DELETE, UPDATE, and SELECT.

Exercises

    Evaluate each of the following three declarations and determine which ones are legal and which ones are not legal. Explain your answer for those that are not legal.

  1. Legal or not legal:
    DECLARE
    emp_rec        emp_rec_type;
    

    A This is not a legal declaration because emp_rec_type must be declared prior to this declaration. A proper declaration would be

    DECLARE
    TYPE emp_rec_type IS record
          (id      INTEGER,
           name    VARCHAR2(35));
    emp_rec        emp_rec_type;
    
  2. Legal or not legal:
    DECLARE
    emp_last_name         %type;
    

    A This is not a legal declaration. The proper declaration would have to include a table and column reference such as

    emp_last_name         emp.l_name%type;
    
  3. Legal or not legal:
    DECLARE
    TYPE   emp_table_type is table of VARCHAR2(55);
    emp_dept_table   emp_table_type;
    

    A This declaration is not legal because the INDEX BY clause is missing. This declaration should look like

    DECLARE
    TYPE   emp_table_type is table of VARCHAR2(55)
    INDEX BY BINARY_INTEGER;
    emp_dept_table   emp_table_type;
    

Page 529

Day 10

Quiz

  1. What are the cursor attributes and what is their purpose?

    A The implicit and explicit cursors each have four attributes, which provide useful information about the cursor. The attributes are %isopen, %found, %notfound, and %rowcount.

  2. How many cursors can you use at a time?

    A There is no predefined limit to the number of cursors a session can have. The only constraint that can limit the number of cursors is the availability of memory to manage the cursors. Also, there is a systemwide limit of cursors, which is defined by the OPEN_CURSOR parameter.

  3. Where is the cursor pointer when the cursor is first opened?

    A When the cursor is opened, the cursor pointer is pointing to immediately prior to the first row.

Exercise

Create a PL/SQL block that determines the top five highest-paid employees from your employee table. Be sure to incorporate the usage of the appropriate cursor attributes. Print these five employees to the screen.

A This exercise can be solved in several different ways. Your solution can include exception handling as well as other methods of processing the data. I have chosen the following method as my solution:

DECLARE
c_emp_name            VARCHAR2(32);
c_sal                        NUMBER(9,2);

CURSOR  emp_cursor is                       -- cursor declaration
SELECT emp_name, pay_type
from employee
ORDER BY pay_rate desc;     -- key to getting top 5 highest-paid employees

BEGIN

OPEN emp_cursor;
FETCH emp_cursor
INTO c_emp_name, c_sal;   --fetch into variables for later use
WHILE emp_cursor%rowcount<=5 and           -- only fetch top 5 employees
   emp_cursor%found                                -- be sure there is data
LOOP
DBMS_OUTPUT (c_emp_name || ` is paid ` || c_sal );
                     -- prints results to screen
FETCH emp_cursor INTO c_emp_name, c_sal;
END LOOP;
CLOSE emp_cursor;                                  -- closes the cursor
END;

Page 530

Day 11

Quiz

  1. Which data manipulation statements can support triggers?

    A INSERT, UPDATE, and DELETE.

  2. What are the four basic parts of a trigger?

    A The event that fires the trigger, the database table on which the trigger is defined, the optional WHEN clause, and the PL/SQL block containing the code to be executed.

  3. In a trigger, what are the correlation names :OLD and :NEW used for?

    A :OLD is used to refer to the values in a row before it is changed. :NEW is used to refer to the values after the row is changed.

  4. What is the name of the system view that can be used to retrieve trigger definitions?

    A The USER_TRIGGERS view shows all triggers you own. In addition, you might want to look at the ALL_TRIGGERS view and the DBA_TRIGGERS view. The ALL_TRIGGERS view adds triggers that others own but which are defined on your tables. If you have database administrator privileges, the DBA_TRIGGERS view lists all triggers defined in the database.

  5. What is a mutating table?

    A A mutating table is one that is in the process of being modified by the SQL statement which fired a trigger. Because the table is being changed it is not in a consistent state, and Oracle does not allow queries against it.

  6. Name some possible uses for triggers.

    A Some possible uses for triggers are enforcing a business rule, enforcing security, logging changes, replication of data, and calculation of column values.

Exercises

  1. Write a set of triggers to maintain the emp_name and dept_name fields redundantly in the emp_dept relation, so that you do not have to join with the employee and department tables just to get a simple department listing.

    A Here is one solution:

    INPUT/
    OUTPUT

    CREATE OR REPLACE TRIGGER emp_dept_names
      BEFORE INSERT OR UPDATE OF emp_id, dept_id ON emp_dept
      FOR EACH ROW
    DECLARE
      redundant_dept_name     department.dept_name%TYPE;
      redundant_emp_name      employee.emp_name%TYPE;
    BEGIN
      --Get the employee's name

Page 531


  BEGIN
    SELECT emp_name INTO redundant_emp_name
      FROM employee
     WHERE employee.emp_id = :NEW.emp_id;
  EXCEPTION
    --the employee record may not exist.
    WHEN OTHERS THEN
      redundant_emp_name := `';
  END;

  --Get the department name
  BEGIN
    SELECT dept_name INTO redundant_dept_name
      FROM department
     WHERE department.dept_id = :NEW.dept_id;
  EXCEPTION
    --the department record may not exist.
    WHEN OTHERS THEN
      redundant_dept_name := `';
  END;

  --Store the employee and department names in the emp_dept record.
  :NEW.dept_name := redundant_dept_name;
  :NEW.emp_name := redundant_emp_name;
END;
/
Trigger created.
CREATE OR REPLACE TRIGGER department_emp_dept
  AFTER UPDATE OF dept_name ON department
  FOR EACH ROW
BEGIN
  UPDATE emp_dept
     SET emp_dept.dept_name = :NEW.dept_name
   WHERE emp_dept.dept_id = :NEW.dept_id;
END;
/
Trigger created.
CREATE OR REPLACE TRIGGER employee_emp_dept
  AFTER UPDATE OF emp_name ON employee
  FOR EACH ROW
BEGIN
  UPDATE emp_dept
     SET emp_dept.emp_name = :NEW.emp_name
   WHERE emp_dept.emp_id = :NEW.emp_id;
END;
/
Trigger created.

ANALYSIS
The first trigger, emp_dept_name, handles inserts and updates on the emp_dept table itself. Whenever a new record is inserted or an existing record updated, the current employee and department names are retrieved from their respective tables and stored with the emp_dept record. The second trigger, department_emp_dept, ensures that any changes to a department's name are propagated to all the related records in the emp_dept table. The third trigger does the same thing for changes to employee names.

Page 532

    Writing these triggers almost leads to a mutation problem. Recall the emp_dept_upd trigger shown in Listing 11.6. It is defined to fire only when the dept_id field is updated; in other words it is defined as AFTER UPDATE OF dept_id ON emp_dept. Removing the words OF dept_id would cause the trigger to fire whenever an emp_dept record was changed. In that case a change to a department name would fire department_emp_dept, which would issue an update against the emp_dept table. That would in turn fire the emp_dept_upd trigger, which would issue an update against the department table, which would be mutating because the SQL statement that started all this was an update against that table.

  1. Write the SQL statements necessary to populate the emp_name and dept_name fields for any existing emp_dept records.

    A This could be done as either one or two updates. Here is a solution done with one UPDATE statement:

    UPDATE emp_dept ed
     SET emp_name = (SELECT emp_name
                       FROM employee e
                      WHERE e.emp_id = ed.emp_id),
          dept_name = (SELECT dept_name
                         FROM department d
                        WHERE d.dept_id = ed.dept_id);
    

Day 12

Quiz

  1. Name the different cursor variable parameter modes and their purposes.

    A The cursor variable argument can have one of three different modes. These

    modes are

  2. What is the scope of a cursor parameter?

    A The cursor parameter is used to pass information into a cursor just as you would pass a parameter into a function or procedure. This can make your code more modular and maintainable. Likewise, you can establish initial or default values for these parameters to make your coding more simplistic.

Exercise

Write a brief package declaration block and the beginning portion of the package body where you declare a cursor variable, and then use this variable in a procedure to open a cursor with a query.

Page 533

A Like many programming designs, your answer can vary slightly from the following. The idea is for you to get practice declaring cursor variables and parameters in the package specification and body.
/* Package Specification  */
CREATE PACKAGE stock_quotes as
TYPE stock_cur_type IS REF cursor;
PROCEDURE get_quotes (stock_cv in out stock_cur_type);
END stock_quotes;

/* Package Body        */

CREATE PACKAGE BODY stock_quotes as
PROCEDURE get_quotes (stock_cv in out stock_cur_type) IS
SELECT * from stocks;
...
END get_quotes;

END stock_quotes;

Day 13

Quiz

  1. What is the difference between a class and an object?

    A A class, or object type as it is called by Oracle, serves as the blueprint for one or more objects. It is just a design, and you might compare it to a table definition. An object, on the other hand, represents an instance of a class. You can create many objects of a given type, just as you can create many records in a table.

  2. What are the allowed return values for an ORDER function?

    A The allowed return values for an ORDER function are 0, _1, and 1. A 0 value means that the two objects being compared are equal. A value of _1 means that the object whose method was called is less than the other object. A value of 1 means that the object whose method was called is greater than the other object.

  3. An object table has one column for each attribute of an object, plus one additional column. What is this additional column used for?

    A The extra column in an object table is used to store the object identifier, which uniquely identifies that object in the database. It is an Oracle-generated value, and is automatically assigned to each object when it is first stored in the table.

  4. How is an object reference different from an object?

    A An object reference functions much like a pointer in a language such as C. It is used to store a reference from one object to another. It is only a pointer, and in order to access the referenced object, you must use that pointer in a query to retrieve the specified object.

Page 534

  1. How many attributes must an object have? How many methods?

    A Objects must have at least one attribute. They do not, however, have to have any methods at all.

  2. What datatypes are allowed for the return value of a MAP function?

    A A MAP function can only return values of type NUMBER, VARCHAR2, or DATE.

Exercises

  1. Write a stored function that creates and returns an object of type building. This function should accept as parameters the building's name, its address, and the manager's employee number. Have the function check the database before creating the new building object to be sure that another building with the same name does not already exist. If another building with the same name does exist, then the function should return null.

    A Here is one solution:

    INPUT/
    OUTPUT

     1: CREATE OR REPLACE FUNCTION CreateBuilding (
     2:   --This is an example of how you can work around the
     3:   --fact that you can't write your own "constructor" for
     4:   --the building object. This stored function serves
     5:   --as a psuedo-constructor. Note however, that Oracle can't
     6:   --force you to call this.
     7:   inBldgName        VARCHAR2,
     8:   inBldgStreet      VARCHAR2,
     9:   inBldgCity        VARCHAR2,
    10:   inBldgStateAbbr   VARCHAR2,
    11:   inBldgZip         VARCHAR2,
    12:   inBldgMgr         employee.emp_id%TYPE
    13:   ) RETURN building AS
    14: TheNewBldg    building;
    15: NoFlag        integer;
    16: BEGIN
    17:   --Check to see if this building already exists in the database.
    18:     SELECT count(*) INTO NoFlag
    19:       FROM buildings
    20:      WHERE BldgName = inBldgName;
    21:
    22:     IF NoFlag > 0 THEN
    23:       RETURN null;
    24:     END IF;
    25:
    26:     --Check to see if the manager employee ID is valid.
    27:       SELECT count(*) INTO NoFlag
    28:         FROM employee
    29:        WHERE emp_id = inBldgMgr;
    30:
    31:       IF NoFlag = 0 THEN
    32:          RETURN null;
    33:       END IF;
    34:
    35:   --All validation checks have been passed, create the new
    36:   --building object.
    37:   TheNewBldg := building (inBldgName

Page 535


38:                         ,address (inBldgStreet
39:                                  ,'' --no second addr line
40:                                  ,inBldgCity
41:                                  ,inBldgStateAbbr
42:                                  ,inBldgZip
43:                                  ,'') --no phone number
44:                         ,inBldgMgr);
45:
46:   RETURN TheNewBldg;
47: END;
48: /

Function created.

 1: SET SERVEROUTPUT ON
 2: DECLARE
 3:   a_building    building;
 4: BEGIN
 5:     --This will succeed
 6:     a_building := CreateBuilding(`The Red Barn',
 7:                                  `101 Pasture Lane',
 8:                                  `Mio','MI','48826',599);
 9:     dbms_output.put_line(`Created: ` || a_building.BldgName);
10:
11:     --This will fail because the building exists.
12:     a_building := CreateBuilding(`East Storage Shed',
13:                                  `101 Pasture Lane',
14:                                  `Mio','MI','48826',599);
15:     dbms_output.put_line(`Created: ` ||
Ânvl(a_building.BldgName,'Nothing'));
16:
17:     --This will fail because the manager does not exist.
18:     a_building := CreateBuilding(`The Blue Barn',
19:                                  `101 Pasture Lane',
20:                                  `Mio','MI','48826',999);
21:     dbms_output.put_line(`Created: ` ||
Ânvl(a_building.BldgName,'Nothing'));
22:
23:  END;
24: /

Created: The Red Barn
Created: Nothing
Created: Nothing

PL/SQL procedure successfully completed.

ANALYSIS
The CreateBuilding function takes five arguments: a building name, street address, city, state abbreviation, and manager ID. It returns an object of type building. The SELECT statement in lines 18_20 of the first segment first checks to see if a building with the same name already exists. Then the SELECT statement in lines 27_29 of the first segment checks to be sure that the manager ID is a valid employee ID. If everything checks out, the building constructor is called in lines 37_44 of the first segment to actually create the building object, which is then returned to the calling program (see line 46 of the first segment).

Page 536

    The PL/SQL block at the end of the listing shows the results of three attempts to create building objects. The first succeeds. The second fails because a building with the same name already exists. The third also fails, but this time because the building manager ID does not represent a valid employee ID.

  1. Modify the building object type definition to use a MAP function for comparisons instead of an ORDER function.

    INPUT/
    OUTPUT
    A Here is one solution:

    1: CREATE OR REPLACE TYPE building AS OBJECT (
    2:   BldgName          VARCHAR2(40),
    3:   BldgAddress       address,
    4:   BldgMgr           INTEGER,
    5:   MEMBER PROCEDURE  ChangeMgr (NewMgr IN INTEGER),
    6:   MAP MEMBER FUNCTION Compare
    7:       RETURN VARCHAR2
    8:   );
    
    Type created.
    
     1: CREATE OR REPLACE TYPE BODY building AS
     2:   MEMBER PROCEDURE  ChangeMgr(NewMgr IN INTEGER) IS
     3:     BEGIN
     4:       BldgMgr := NewMgr;
     5:     END;
     6:
     7:   MAP MEMBER FUNCTION Compare
     8:   RETURN VARCHAR2 IS
     9:     BEGIN
    10:       RETURN BldgName;
    11:     END;
    12: END;
    13: /
    
    Type body created.
    

    ANALYSIS
    This version of the building object is much the same as the one you first created in Listing 13.7, except that it has a MAP function defined instead of an ORDER function. This MAP function, declared in lines 6_7 of the first segment and defined in lines 7_11 of the second segment, simply returns the building name. When comparing objects of type building, Oracle will call this function and base the comparison on the values returned.

Day 14

Quiz

  1. True or False: Logic errors are easier to debug than syntax errors.

    A False. You might not even know logic errors exist until years later.

Page 537

  1. Missing a semicolon is what type of error?

    A A logic error.

  2. Provide the answer to the calculation 6 + 4/2 = ?

    A 8. You perform the division calculation first and then add the result to 6.

  3. True or False: Oracle comes with a built-in debugging package.

    A False. You can create one, purchase one, use pipes, or use the DBMS_OUT package.

  4. True or False: Proper planning reduces errors in the future.

    A True. Proper planning helps you to improve the application and do it right the

    first time.

  5. True or False: Commenting code is a waste of time.

    A False. Comments improve readability of code and clarify the intent of the application programmer.

  6. True or False: Formatting code is not necessary.

    A False. Without proper formatting techniques, the code is hard to read and even harder to follow.

Exercises

  1. Use the DEBUG package to troubleshoot the code in Listing 14.2.

    A One possible answer is

    DECLARE
        v_MyNumber NUMBER := 0;
    BEGIN
        DEBUG.ERASE;
        LOOP
            IF v_MyNumber = 7 THEN
                 EXIT;
            END IF;
        v_MyNumber := v_MyNumber + 2;
            DEBUG.OUT(`v_MyNumber',v_MyNumber);
        END LOOP;
    END;
    
  2. Use the DBMS_OUTPUT package to troubleshoot the code in Listing 14.2.

    A One possible answer is

    SET SERVEROUTPUT ON
    
    DECLARE
        v_MyNumber NUMBER := 0;
    BEGIN
        LOOP
            IF v_MyNumber = 7 THEN
                 EXIT;
    
    
    

Page 538

        END IF;
    v_MyNumber := v_MyNumber + 2;
        DBMS_OUTPUT.PUT_LINE(`The valye of v_MyNumber is: ` || v_MyNumber);
    END LOOP;
END;

Day 15

Quiz

  1. Could you use recursion to generate an organizational chart as long as you had the ID of your immediate boss coded in your employee record?

    A Yes, but a large organizational chart will make for slower execution time than an alternative method.

  2. Should you use recursion as much as possible?

    A No. In fact, you will probably rarely use recursion.

  3. What is the largest size of a large object?

    A Four gigabytes.

  4. Can you write to external files?

    A Currently, you can only read from external files.

  5. When copying LOBs from one row to another, is only a new locator copied?

    A Not only is the newly created locator copied, but the entire LOB from the row is copied as well. If you have some 4-gigabyte objects, this table can eat up storage space fast!

Exercise

Rewrite the code in Listing 15.1, but as a loop instead of a recursive function. Provide the result of 6 factorial (6!).

A One possible answer is

DECLARE
     v_MyNum NUMBER := 6;
     v_Factorial NUMBER := 1;
BEGIN
     LOOP
          IF v_MyNum = 0 THEN
               EXIT;
          ELSE
               v_Factorial := v_Factorial * v_MyNum;
          END IF;
          v_MyNum := v_MyNum - 1;
     END LOOP;
     DBMS_OUTPUT.PUT_LINE(`The factorial is:  ` || v_Factorial);
END;
The factorial is:  720

Page 539

Day 16

Quiz

  1. How is a transaction ended?

    A A transaction is ended when it is committed or when it is rolled back.

  2. What is the difference between row locking and table locking?

    A To summarize, row locks are enabled when a specific row is being modified by a DML statement. Likewise, a table lock is acquired either explicitly or implicitly when either a row or a table is being modified. Refer to the section in this chapter regarding locks, "Locking."

  3. What is the purpose of a savepoint?

    A Savepoints are like bookmarks within a transaction; they facilitate the rollback of a transaction to some intermediate point. This intermediate point is defined by the placement of the savepoint.

Exercise

Write a PL/SQL block that establishes a savepoint, inserts a single record into the employee table, commits the data if the new record does not replicate an existing record, or rolls back the data if the new record insert fails.

A Here is one solution:

SAVEPOINT exercise;            -- use this to roll back to
INSERT INTO employee VALUES (1, `Loraine Williams',2,4000.00,'S');
COMMIT;     -- saves the data if insert was successful
            -- this is not executed if there is an exception
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN   -- exception handler
ROLLBACK;                    -- back out data from insert

Day 17

Quiz

  1. For DML and DDL statements, and also for queries, what punctuation must not occur at the end of the query?

    A You must not end queries, DDL statements, or DML statements with a semicolon.

  2. For anonymous blocks, is all punctuation required?

    A Anonymous blocks require all punctuation.

  3. What is the largest size a PL/SQL variable can hold in kilobytes?

    A The largest value a PL/SQL variable can hold is 32KB.

Page 540

  1. Why is processing queries and SQL with the DBMS_SQL package considered dynamic?

    A Unlike fixed queries, these queries are created during runtime operations and executed in real time, thus making them dynamic.

Exercises

  1. Write a SQL query to show all records and all fields to the screen where the value of MyRow is greater than or equal to 2.

    A One possible answer is

    DECLARE
    /* The purpose of this PL/SQL block is to demonstrate
       executing queries within PL/SQL through the use of the
       DBMS_SQL package.  We will simply display the output to
       screen with the DBMS_OUTPUT package */
    
         v_CursorID  NUMBER; -- Variable assigned to value from OPEN_CURSOR
         v_SelectRecords  VARCHAR2(500); -- SQL stored as string to select
                                            Ârecords
         v_NUMRows  INTEGER; -- Number of rows processed - of no use
         v_MyNum INTEGER;
         v_MyText VARCHAR2(50);
    
    BEGIN
         v_CursorID := DBMS_SQL.OPEN_CURSOR; -- Get the Cursor ID
         v_SelectRecords := `SELECT * from MyTable
              WHERE MyRow >= 2'; -- SQL to view records
    
    
         DBMS_SQL.PARSE(v_CursorID,v_SelectRecords,DBMS_SQL.V7);
              /* Perform syntax error checking */
    
    /*     DBMS_SQL.BIND_VARIABLE(v_CursorID, `:mynum',1);
         DBMS_SQL.BIND_VARIABLE(v_CursorID, `:mytext','One'); */
    
         DBMS_SQL.DEFINE_COLUMN(v_CursorID,1,v_MyNum);
         DBMS_SQL.DEFINE_COLUMN(v_CursorID,2,v_MyText,50);
    
         v_NumRows := DBMS_SQL.EXECUTE(v_CursorID);
              /* Execute the SQL code  */
    LOOP
         IF DBMS_SQL.FETCH_ROWS(v_CursorID) = 0 THEN
              EXIT;
         END IF;
    
         DBMS_SQL.COLUMN_VALUE(v_CursorId,1,v_MyNum);
         DBMS_SQL.COLUMN_VALUE(v_CursorId,2,v_MyText);
    
         DBMS_OUTPUT.PUT_LINE(v_MyNum || ` ` || v_MyText);
    
    END LOOP;
    
    

Page 541

    EXCEPTION
         WHEN OTHERS THEN
                   RAISE; -- raise if some other unknown error
    
         DBMS_SQL.CLOSE_CURSOR(v_CursorID); -- Close the cursor
    
    END; -- End PL/SQL block
    
    2 Two
    3 Three
    4 Four
    4 Four
    
  1. Write a SQL query to delete all records with MyRow equal to 4.
    A Here's one solution:
    DECLARE
    /* The purpose of this PL/SQL block is to demonstrate the use
       of DML statements by deleting two records where MyRow
       has a value of 4 */
    
         v_CursorID  NUMBER; -- Variable assigned to value from OPEN_CURSOR
         v_DeleteRecords  VARCHAR2(500); -- SQL stored as string to insert
                                                                                           Ârecords
         v_NUMRows  INTEGER; -- Number of rows processed - of no use
    
    BEGIN
         v_CursorID := DBMS_SQL.OPEN_CURSOR; -- Get the Cursor ID
         v_DeleteRecords := `DELETE FROM MyTable
                   where MyRow = :mynum';
    
    /*  Update the record */
    
         DBMS_SQL.PARSE(v_CursorID,v_DeleteRecords,DBMS_SQL.V7);
              /* Perform syntax error checking */
         DBMS_SQL.BIND_VARIABLE(v_CursorID, `:mynum',4);
          v_NumRows := DBMS_SQL.EXECUTE(v_CursorID);
              /* Execute the SQL code  */
         DBMS_OUTPUT.PUT_LINE(`The number of records just processed is: `
              || v_NUMRows);
    
    
    EXCEPTION
         WHEN OTHERS THEN
                   RAISE; -- raise if some other unknown error
    
         DBMS_SQL.CLOSE_CURSOR(v_CursorID); -- Close the cursor
         COMMIT;
    END; -- End PL/SQL block
    
    Your data should look like:
    MYROW MYDESC
    ----- --------------------------------------------------
        1 One
        2 Two
        3 Three
    

Page 542

  1. Write the DML code to update the record where MyRow is equal to 1. Change the description to say It Worked.

    A One possible answer is

    DECLARE
    /* The purpose of this PL/SQL block is to demonstrate the use
       of DML statements by updating one records where MyRow
       has a value of 1 */
    
         v_CursorID  NUMBER; -- Variable assigned to value from OPEN_CURSOR
         v_UpdateRecords  VARCHAR2(500); -- SQL stored as string to insert
                                                                                           Ârecords
         v_NUMRows  INTEGER; -- Number of rows processed - of no use
    
    BEGIN
         v_CursorID := DBMS_SQL.OPEN_CURSOR; -- Get the Cursor ID
         v_UpdateRecords := `UPDATE MyTable
              Set MyDesc = :mytext
                   where MyRow = :mynum';
    
    /*  Update the record */
    
         DBMS_SQL.PARSE(v_CursorID,v_UpdateRecords,DBMS_SQL.V7);
              /* Perform syntax error checking */
         DBMS_SQL.BIND_VARIABLE(v_CursorID, `:mynum',1);
         DBMS_SQL.BIND_VARIABLE(v_CursorID, `:mytext','It Worked');
         v_NumRows := DBMS_SQL.EXECUTE(v_CursorID);
              /* Execute the SQL code  */
         DBMS_OUTPUT.PUT_LINE(`The number of records just processed is: `
              || v_NUMRows);
    
    
    EXCEPTION
         WHEN OTHERS THEN
                   RAISE; -- raise if some other unknown error
    
         DBMS_SQL.CLOSE_CURSOR(v_CursorID); -- Close the cursor
         COMMIT;
    END; -- End PL/SQL block
    
    MYROW MYDESC
    ----- --------------------------------------------------
        1 It Worked
        2 Two
        3 Three
    
  2. Write the DDL code required to drop the table called MyTable.

    A One possible answer is

    DECLARE
    /* The purpose of this PL/SQL block is to delete a table
       called MyTable, which has two columns of type INTEGER and
       the second column of type VARCHAR2(50).  This uses the
       DBMS_SQL package to execute DDL statements */
    
    

Page 543

     v_CursorID  NUMBER; -- Variable assigned to value from OPEN_CURSOR
     v_CreateTableString  VARCHAR2(500); -- SQL stored as string to drop
                                            Âtable
     v_NUMRows  INTEGER; -- Number of rows processed - of no use

BEGIN
     v_CursorID := DBMS_SQL.OPEN_CURSOR; -- Get the Cursor ID
     v_CreateTableString := `DROP TABLE MyTable'; -- Write SQL code to drop
                                                                                                                   Âtable

     DBMS_SQL.PARSE(v_CursorID,v_CreateTableString,DBMS_SQL.V7);
          /* Perform syntax error checking */
     v_NumRows := DBMS_SQL.EXECUTE(v_CursorID);
          /* Execute the SQL code  */

EXCEPTION
     WHEN OTHERS THEN
          RAISE; -- raise if some other unknown error

     DBMS_SQL.CLOSE_CURSOR(v_CursorID); -- Close the cursor
END; -- End PL/SQL block

Day 18

Quiz

  1. What is the difference between PUT and PUT_LINE with the UTL_FILE package or even the DBMS_OUTPUT package?
    A PUT_LINE adds a newline character after each line written to the file, whereas PUT does not write out the newline character. You would have to use the NEW_LINE statement with PUT in order for PUT to behave identically to PUT_LINE.
  2. What is the maximum number of bytes that can be read into a buffer when using UTL_FILE?

    A 1022 bytes.

  3. What is the maximum number of bytes that can be output to a file when using UTL_FILE?

    A 1023 bytes.

  4. When using FCLOSE_ALL, what boolean value will be returned by testing to see if the file is open using IS_OPEN?

    A Even though the files are truly closed, the files are not marked as closed, so IS_OPEN will return a value of true.

  5. What suppresses output from PUT or PUT_LINE when using the DBMS_OUTPUT package?

    A Either GET_LINE or GET_LINES will cause any form of PUT not to default to the screen.

Page 544

  1. What is the maximum number of characters allowed in a buffer line when using DBMS_OUTPUT?

    A 255 characters—any more will raise an error.

  2. What is the maximum number of characters that can possibly be allocated to the buffer when using DBMS_OUTPUT?

    A 1,000,000 total characters. If the buffer limit is exceeded, an error is raised.

Exercises

  1. From the code in Listing 18.6, output the data in a fixed block format. The output should be SSNO with a length of 11 bytes formatted as ###-##-####, FirstName at 20 bytes, and LastName at 30 bytes. Remember, VARCHAR2 data does not pad spaces to the right. You could either use a character function to fill the data with spaces or assign the variables to type CHAR before outputting the data. Name the output file PRESFIX.TXT.

    A One possible answer is

    DECLARE
    -- Variables to write out to text file called presexp.csv
         v_SSNO       pres.ssno%TYPE;
         v_FirstName  CHAR(20);
         v_LastName   CHAR(30);
    --Declare filehandle
         v_MYFileHandle UTL_FILE.FILE_TYPE;
    -- Cursor declaration to fetch data from table, row by row
    CURSOR c_PRES IS
         SELECT * from PRES;
    BEGIN
    -- Open File for writing
         v_MYFileHandle := UTL_FILE.FOPEN(`c:\','presfix.txt','w');
    -- Open the cursor
         OPEN c_PRES;
         LOOP
    -- Get each row, one at a time
              FETCH c_PRES INTO v_SSNO,v_FirstName,v_LastName;
    -- Output as FIXED BLOCK file
              EXIT WHEN c_PRES%NOTFOUND;
              UTL_FILE.PUT_LINE(v_MYFileHandle, SUBSTR(v_SSNO,1,3) || `-'
                   || SUBSTR(v_SSNO,4,2) || `-' || SUBSTR(v_SSNO,6,4)
                   || v_FirstName || v_LastName);
          END LOOP;
    -- Close Cursor
         CLOSE c_PRES;
    -- Close CSV file
         UTL_FILE.FCLOSE(v_MYFileHandle);
    END;
    
    
    001-01-0111George              Washington
    005-12-4745Thomas              Jefferson
    
    

PAGE 545

    223-44-5726Richard             Nixon
    378-11-2475Jimmy               Carter
    541-12-9744Abraham             Lincoln
    587-14-5961John                Kennedy
    665-47-4112Theodore            Roosevelt
    725-13-9511George              Bush
    998-21-1247Ronald              Reagan
    
  1. From Exercise 1, add DBMS_OUTPUT.PUT_LINE to display the lines to the screen as they are output to the file.

    A One possible answer is

    DECLARE
    -- Variables to write out to text file called presexp.csv
         v_SSNO       pres.ssno%TYPE;
         v_FirstName  CHAR(20);
         v_LastName   CHAR(30);
    --Declare filehandle
         v_MYFileHandle UTL_FILE.FILE_TYPE;
    -- Cursor declaration to fetch data from table, row by row
    CURSOR c_PRES IS
         SELECT * from PRES;
    BEGIN
    -- Open File for writing
         v_MYFileHandle := UTL_FILE.FOPEN(`c:\','presfix.csv','w');
    -- Open the cursor
         OPEN c_PRES;
         LOOP
    -- Get each row, one at a time
              FETCH c_PRES INTO v_SSNO,v_FirstName,v_LastName;
    -- Output as FIXED BLOCK file
              EXIT WHEN c_PRES%NOTFOUND;
              UTL_FILE.PUT_LINE(v_MYFileHandle, SUBSTR(v_SSNO,1,3) || `-'
                   || SUBSTR(v_SSNO,4,2) || `-' || SUBSTR(v_SSNO,6,4)
                   || v_FirstName || v_LastName);
              DBMS_OUTPUT.PUT_LINE(v_SSNO || v_FirstName || v_LastName);
         END LOOP;
    -- Close Cursor
         CLOSE c_PRES;
    -- Close CSV file
         UTL_FILE.FCLOSE(v_MYFileHandle);
    END;
    
    001010111George              Washington
    005124745Thomas              Jefferson
    223445726Richard             Nixon
    378112475Jimmy               Carter
    541129744Abraham             Lincoln
    587145961John                Kennedy
    665474112Theodore            Roosevelt
    725139511George              Bush
    998211247Ronald              Reagan
    

Page 546

Day 19

Quiz

  1. If the server goes down for two days (Monday to Tuesday), and a job with an execution of SYSDATE + 7 was supposed to run when the server went down (Tuesday), will the job always run on the original day of the week (run every Tuesday)?

    A No, because the new SYSDATE is assigned when the server is restored (Wednesday). The job will now be running every Wednesday until the job is altered, removed, or another problem occurs.

  2. Why must you use two single quotes around parameters specified in SUBMIT that used to take only one set of single quotes?

    A When Oracle parses the data and removes the quotes, any part that does not have two sets of single quotes and is a string parameter will be stripped down to no single quotes and thus cause the job to fail.

  3. Can you alter someone else's job?

    A Only if you know his or her login and password and sign on as that person. In other words…no!

  4. Is there any way to assign your own job number to a job?

    A Use ISUBMIT.

  5. What interval would you use to run a procedure every hour on the hour starting from the current date?

    A SYSDATE + 1/24.

  6. If you send a message to a full pipe, how long will you wait before you abort the process?

    A The default of maxwait is 1000 days! A more typical approach is to set the wait period for up to 60 seconds.

  7. What is the maximum length of the message buffer?

    A A maximum of 4096 bytes can be held in the message buffer before an overflow message will be displayed.

Exercises

  1. Write the code to submit a procedure called PAYDAY, where the parameters are FRIDAY, Bi_Monthly, and 6. The job should always execute at 4 a.m. on Saturday.

    A One possible answer is

    DECLARE
         v_JobNum  BINARY_INTEGER;
    
    

Page 547

    BEGIN
         DBMS_JOB.SUBMIT(v_JobNum,'PAYDAY(`'FRIDAY'',''BI_Monthly,6);',SYSDATE,
              `NEXT_DAY(TRUNC(SYSDATE),''SATURDAY'') + 4/24');
    END;
    
  1. Write the code to view the JOB, last second run, and WHAT from USER_JOBS.

    A One possible answer is

    SELECT JOB,LAST_SEC,WHAT from USER_JOBS;
    
  2. Write the code to submit job 200 once per day starting from SYSDATE for the procedure EASY, which has no parameters.

    A One possible answer is

    DECLARE
    BEGIN
         DBMS_JOB.ISUBMIT(200,'EASY;',SYSDATE,'SYSDATE + 1');
    END;
    
  3. Write the code to alter job 200 to execute once per week for the interval

    (SYSDATE + 7).

    A One possible answer is

    BEGIN
         DBMS_JOB.INTERVAL(200,'SYSDATE+7');
    END;
    
  4. Write the code to remove job 200.

    A One possible answer is

    BEGIN
         DBMS_JOB.REMOVE(200);
    END;
    
  5. From Listing 19.22, add the ELSIF procedures to handle RAW and ROWID datatypes.

    A One possible answer is

    DECLARE
         v_statpipe1 integer; -- status of private pipe
         v_statpipe2 integer; -- status of public pipe
         v_holdtype INTEGER; -- holds status of next item type
         v_holdchar VARCHAR2(100);
         v_holddate DATE;
         v_holdnum NUMBER;
         v_holdraw RAW(4000);
         v_holdrowid ROWID;
    BEGIN
    -- start procedure of getting message from private pipe
         v_statpipe1 := DBMS_PIPE.RECEIVE_MESSAGE(`myprivatepipe',15);
         DBMS_PIPE.UNPACK_MESSAGE(v_holdchar);
         DBMS_OUTPUT.PUT_LINE(v_holdchar);
              -- display first datatype from message
    
         DBMS_PIPE.UNPACK_MESSAGE(v_holdchar);
         DBMS_OUTPUT.PUT_LINE(v_holdchar);
              -- display second datatype from message
    
    

Page 548

    
    -- start procedure of getting message from public pipe
         v_statpipe2 := DBMS_PIPE.RECEIVE_MESSAGE(`mypublicpipe',10);
         LOOP
              v_holdtype := DBMS_PIPE.NEXT_ITEM_TYPE;
              IF v_holdtype = 0 THEN EXIT;
              ELSIF v_holdtype = 6 THEN
                   DBMS_PIPE.UNPACK_MESSAGE(v_holdnum);
              ELSIF v_holdtype = 9 THEN
                   DBMS_PIPE.UNPACK_MESSAGE(v_holdchar);
              ELSIF v_holdtype = 12 THEN
                   DBMS_PIPE.UNPACK_MESSAGE(v_holddate);
              ELSIF v_holdtype = 11 THEN
                   DBMS_PIPE.UNPACK_MESSAGE_ROWID(v_holdrowid);
              ELSIF v_holdtype = 23 THEN
                   DBMS_PIPE.UNPACK_MESSAGE_RAW(v_holdraw);
              END IF;
         END LOOP;
    -- display all three types of data
         DBMS_OUTPUT.PUT_LINE(v_holdchar || ` ` ||
              v_holddate || ` ` || v_holdnum);
    END;
    
  1. From Listing 19.22, all data read into the public pipe should be inserted into a table called PIPETAB with the following schema: MyString VARCHAR2, MyDate DATE, and MyNum NUMBER. Make sure that you run the CREATE_PIPE procedure from Listing 19.21 so that you have data in the pipes for placing in the table.

    A You would first create the table PIPETAB with

    CREATE TABLE PIPETAB (
         MyString VARCHAR2(100),
         MyDate DATE,
         MyNumber NUMBER)
    /
    
    DECLARE
         v_statpipe2 integer; -- status of public pipe
         v_holdtype INTEGER; -- holds status of next item type
         v_holdchar VARCHAR2(100);
         v_holddate DATE;
         v_holdnum NUMBER;
         v_holdraw RAW(4000);
         v_holdrowid ROWID;
    BEGIN
    
    -- start procedure of getting message from public pipe
         v_statpipe2 := DBMS_PIPE.RECEIVE_MESSAGE(`mypublicpipe',10);
         LOOP
              v_holdtype := DBMS_PIPE.NEXT_ITEM_TYPE;
              IF v_holdtype = 0 THEN EXIT;
              ELSIF v_holdtype = 6 THEN
                   DBMS_PIPE.UNPACK_MESSAGE(v_holdnum);
              ELSIF v_holdtype = 9 THEN
                   DBMS_PIPE.UNPACK_MESSAGE(v_holdchar);
              ELSIF v_holdtype = 12 THEN
    
    

Page 549


         DBMS_PIPE.UNPACK_MESSAGE(v_holddate);
          ELSIF v_holdtype = 11 THEN
               DBMS_PIPE.UNPACK_MESSAGE_ROWID(v_holdrowid);
          ELSIF v_holdtype = 23 THEN
               DBMS_PIPE.UNPACK_MESSAGE_RAW(v_holdraw);
          END IF;
     END LOOP;
-- display all three types of data
     DBMS_OUTPUT.PUT_LINE(v_holdchar || ` ` || v_holddate
          || ` ` || v_holdnum);
-- Insert Three Data Elements into the table
INSERT INTO PIPETAB(MyString,MyDate,MyNumber)
     VALUES(v_holdchar,v_holddate,v_holdnum);
COMMIT;

END;
You can verify this by typing SELECT * from PIPETAB;.

Day 20

Quiz

  1. What is the maximum length of an alert name?

    A 30 characters. It cannot begin with ORA$, which is reserved by Oracle.

  2. What is the maximum length of the message?

    A 1800 bytes.

  3. What datatype is the message sent as?

    A Only a string type VARCHAR2(1800) is passed and received.

  4. If 20 sessions are monitoring for an alert and the alert is sent, how many of those sessions receive the signaled alert?

    A Unlike pipes, all 20 sessions will receive the alert.

  5. Alerts require a(n) __________ because you are dealing more on a transactional level, whereas pipes do not.

    A Commit

Exercises

  1. Change the code in Listing 20.3 to wait for any alert, and also register for two more alerts called `my_test' and `extra_alert'. Store the name of the alert that is signaled in a variable entitled alert_name of type VARCHAR2(30). After the alert has been handled, remove all registered alerts.

    A One possible answer is

    DECLARE
        message VARCHAR2(1800); -- Display Incoming Message from Alert
        alert_name VARCHAR2(30); -- Hold name of alert which was received
        status INTEGER; -- Holds Status 0 if success, 1 if timed out
    
    
    

Page 550

    
    
    BEGIN
         DBMS_ALERT.REGISTER(`emp_change'); -- Registers for Alert emp_change
         DBMS_ALERT.REGISTER(`my_test'); -- Registers for Alert my_test
         DBMS_ALERT.REGISTER(`extra_alert'); -- Registers for Alert extra_alert
         DBMS_ALERT.WAITANY(alert_name,message,status,600); -- Wait for alert
         DBMS_OUTPUT.PUT_LINE(message); -- Display Message
         DBMS_ALERT.REMOVEALL; -- Remove Registration for Alert
    END;
    
  1. Write a loop that will continually execute until the value of FIRE equals 1, which will then trigger the alert called `a_fire', which will pass the message `A Fire has Broken Out'.

    A One possible answer is

    DECLARE
       IS_FIRE INTEGER := 0;
    BEGIN
       LOOP
            IF IS_FIRE = 1 THEN
                DBMS_ALERT.SIGNAL(`a_fire','A Fire has Broken Out');
                EXIT;
            END IF;
            CHECKFIRE; -- procedure called to check for fire
       END LOOP;
    END;
    

Day 21

Quiz

  1. List several of the options available to access the Oracle database through Java.

    A Java can access a relational database through the use of J/SQL or JDBC. J/SQL is the integration of SQL code within Java. JDBC is a driver-based approach to accessing the database from Java.

  2. What are the differences between PL/SQL and Java?

    A There are several differences between PL/SQL and Java. To summarize them, I would say that Java lacks the data manipulation capabilities that PL/SQL provides. PL/SQL does not take full advantage of some common object-oriented features such as classes and inheritance. The differences between PL/SQL and Java are important to know because they will guide you to choosing the appropriate language for your application.

  3. What role does JDBC play in Internet-to-database connectivity?

    A JDBC allows Java applications to have data access from your Oracle database. JDBC uses a driver-based approach to database connectivity, which is very similar to the ODBC approach.

    Table of Contents

    Используются технологии uCoz