Showing posts with label Oracle. Show all posts
Showing posts with label Oracle. Show all posts

Saturday, July 3, 2010

Nested Cursors: Reading from Multiple Tables


The following is an example of how to loop through a cursor within a cursor to generate a report as shown. This uses the HR schema supplied by Oracle.
DECLARE 
    ------------------------------------------------------- 
    -- This cursor pulls 'Clerks' from the Jobs table.  
    ------------------------------------------------------- 
    CURSOR cur_job IS 
      SELECT job_id 
             , job_title 
        FROM jobs 
       WHERE job_title LIKE '%Clerk%'; 
    rec_job cur_job%ROWTYPE; -- record layout matches cursor 
    ------------------------------------------------------- 
    -- This cursor pulls employees with the parameter for 
    -- the Job_Id being supplied on the open of the employee 
    -- cursor.  
    ------------------------------------------------------- 
    CURSOR cur_emp ( 
      inp_job VARCHAR2 ) IS 
      SELECT RPAD(last_name, 10) AS Last_Name 
             , salary 
        FROM employees 
       WHERE job_id = inp_job 
         AND salary <= 2500 
       ORDER BY salary; 
    rec_emp cur_emp%ROWTYPE; -- record layout matches cursor 
BEGIN 
    OPEN cur_job; 

    ------------------------------------------------------- 
    -- In the outer loop, fetch one record from the Jobs 
    -- cursor, and print the info. 
    ------------------------------------------------------- 
    LOOP 
        FETCH cur_job INTO rec_job; 

        Exit WHEN cur_job%NOTFOUND; 

        dbms_output.PUT_LINE(CHR(10) -- line break 
                             ||'Job: ' 
                             ||rec_job.job_title 
                             ||CHR(10) 
                             ||'---------------------'); 

        ----------------------------------------------------- 
        -- in the inner loop, open the employee cursor using 
        -- the Job_Id from the Jobs cursor. Print the employee 
        -- name, and salary, before closing to get another 
        -- jobs record. 
        ----------------------------------------------------- 
        OPEN cur_emp (rec_job.job_id); 

        LOOP 
            FETCH cur_emp INTO rec_emp; 

            Exit WHEN cur_emp%NOTFOUND; 

            dbms_output.PUT_LINE(rec_emp.last_name 
                                 ||' $' 
                                 ||rec_emp.salary); 
        END LOOP; 

        CLOSE cur_emp; 
    ----------------------------------------------------- 
    END LOOP; 

    CLOSE cur_job; 
END; 
 
 
Tiny URL: http://tinyurl.com/nest-cur

Thursday, June 24, 2010

New in 11g: Continue and Continue When

CONTINUE immediately moves to the next iteration of the loop.
CONTINUE WHEN conditionally moves to the next iteration of the loop.
BEGIN 
  FOR idx IN 1..3 
  LOOP 
    dbms_output.PUT_LINE('-----------------------------------'); 
    dbms_output.PUT_LINE('Idx ' 
    ||idx 
    ||': BeFore Continue. Mod(Idx,2) = ' 
    ||MOD(idx,2)); 
    --------------------------------------------------- 
    --    If Mod(i,2) = 0 THEN --{ This is the 
    --        Continue;        -- same as the 
    --    End If;              -- Continue When below.} 
    --------------------------------------------------- 
    CONTINUE 
  WHEN MOD(idx,2) = 0; 
    dbms_output.PUT_LINE ('After Continue. Print If Mod(Idx,2) is not 0.'); 
  END LOOP; 
END;
/
Notes:
  • When using CONTINUE in a simple loop, make sure you increment your loop before the CONTINUE so you don't create an infinite loop.
  • When using CONTINUE in a While loop, try using a GOTO and a label.
  • Inlining impacts every call to CONTINUE and CONTINUE WHEN.

Wednesday, June 23, 2010

New in 10: Compile-Time Warnings

This will allow you to improve code before it runs live.

"ALTER SESSION" is recommended, as "ALTER SYSTEM" takes too much overhead, or use "ALTER PROCEDURE ... COMPILE."

Find message descriptions by SHOW ERRORS or query User_Errors (PLW-)
CategoryMsg #sExample
Severe5000-5999Aliasing problems with parameters
Informational6000-6249Code that can never run
Performance7000-7249Passing a VARCHAR2 value to a NUMBER column

value_clause::=
{ ENABLE | DISABLE | ERROR* }:
{ All| Severe| Informational| Performance|
{ Integer| (Integer [, Integer ] ...)}}
* 'Error' treats a particular message as an error instead of a warning.

Examples:
Alter Session Set Plsql_Warnings = 
               'Enable:Severe', 'Disable:Informational';
Alter Session Set Plsql_Warnings = 
               'Disable:All';
Alter Session Set Plsql_Warnings = 
               'Disable:5000', 'Enable:5001','Error:5002';
Alter Session Set Plsql_Warnings = 
               'Enable:(5000,5001,5002)','Disable:6000,6001)';
Alter Procedure prog_x Compile Plsql_Warnings=
               'Enable:Performance';
Call Dbms_Warning.Set_Warning_Setting_String
               ('Enable:All' ,'Session');
To see how it's currently set:
BEGIN
DBMS_OUTPUT.PUT_LINE('$$PLSQL_WARNINGS = ' || $$PLSQL_WARNINGS);
END;
/

Tuesday, June 22, 2010

PLSql_Optimize_Level or Pragma Inline for Subprogram Inlining

Subprogram Inlining - The compiler puts the actual code of a subprogram where the call is.

Pro: Improves performance of the program.
Con: Compile time is extended. Program size is larger.



PLSQL_OPTIMIZE_LEVEL controls the degree to which the compiler can rearrange code to make your PL/SQL code run faster.

Alter Procedure Proc_x 
          Compile Plsql_Optimize_Level = ?; 

Alter Session Set PlSql_Optimize_Level = ?;
Alter System  Set PlSql_Optimize_Level = ?;
0Forfeits most of performance gains of 10g
1Moderate optimization: May eliminate unnecessary computations and exceptions.
2Agressive optimization: (Default) Like 1, but may also move code.
3The compiler performs automatic subprogram inlining where appropriate. (new in 11g) 


PRAGMA INLINE ( identifier , { 'YES' | 'NO' } ) ;

Create Or Replace Procedure Inline_Test
     ( Fld Varchar2 ) Is
     Pragma Inline(Some_Function,'YES');
Begin
     For Idx In 1..10 Loop
          Dbms_Output.Put_Line(Some_Function('abc'));
     End Loop;
End;

Besides procedures, and functions, it also impacts every call to CASE, CONTINUE-WHEN, EXECUTE IMMEDIATE, EXIT-WHEN, LOOP, and RETURN statements.

Monday, June 21, 2010

Use Pragma Exception_Init for Error Handling

Format: PRAGMA EXCEPTION_INIT ( Exception_Name , SqlCode# )

This pragma (compiler directive) is necessary in two situations:
  • Use this to name any unnamed ORA- error that might be raised by your program. If you don't, you have to catch those errors with 'WHEN OTHERS.'

  • For a user-defined exception, assign a code between –20999 and –20000 to SQLCode so it can be raised.
Situation #1:


For instance, if ORA-00068 had a possibility of popping up when your program ran, you'd want to code as shown in the example.
ORA-00068: invalid value string for parameter string, must be between string and string
DECLARE
    e_InvalParam Exception;
    Pragma Exception_Init(e_InvalParam, -68);
BEGIN
    ...
EXCEPTION
    when e_InvalParam then ...
END;

The following ORA- errors have already been named.
(See the package named Standard in All_Source.)
Pragma Exception_Init( Access_Into_Null        ,' -6530  ');
Pragma Exception_Init( Case_Not_Found          ,' -6592  ');
Pragma Exception_Init( Collection_Is_Null      ,' -6531  ');
Pragma Exception_Init( Cursor_Already_Open     ,' -6511  ');
Pragma Exception_Init( Dup_Val_On_Index        ,' -0001  ');
Pragma Exception_Init( Invalid_Cursor          ,' -1001  ');
Pragma Exception_Init( Invalid_Number          ,' -1722  ');
Pragma Exception_Init( Login_Denied            ,' -1017  ');
Pragma Exception_Init( No_Data_Found           ,    100   );
Pragma Exception_Init( No_Data_Needed          ,' -6548  ');
Pragma Exception_Init( Not_Logged_On           ,' -1012  ');
Pragma Exception_Init( Program_Error           ,' -6501  ');
Pragma Exception_Init( Rowtype_Mismatch        ,' -6504  ');
Pragma Exception_Init( Self_Is_Null            ,' -30625 ');
Pragma Exception_Init( Storage_Error           ,' -6500  ');
Pragma Exception_Init( Subscript_Beyond_Count  ,' -6533  ');
Pragma Exception_Init( Subscript_Outside_Limit ,' -6532  ');
Pragma Exception_Init( Sys_Invalid_Rowid       ,' -1410  ');
Pragma Exception_Init( Timeout_On_Resource     ,' -0051  ');
Pragma Exception_Init( Too_Many_Rows           ,' -1422  ');
Pragma Exception_Init( Userenv_CommitScn_Error ,' -1725  ');
Pragma Exception_Init( Value_Error             ,' -6502  ');
Pragma Exception_Init( Zero_Divide             ,' -1476  ');



Situation #2:

I want to check for a valid code, and raise an error if the value is wrong.

Procedure p_CkCode
                ( i_Emp# NUMBER
                , i_Code NUMBER) is
    v_Code    NUMBER;
    v_SqlCode NUMBER;
    
    e_InvalCode Exception; 
    Pragma Exception_Init(e_InvalCode,-20456); 
Begin
    ...
Exception
    when e_InvalCode then
        v_SqlCode := SqlCode;
        Insert Into AuditTrack (errors)
                         values(i_emp#||’ ’||v_SqlCode);
        Raise;
End;

By default, all user exceptions have a SqlCode of 1.
The code must be between –20,000 and –20,999. Anything else will raise ORA-21000.

If no code number was assigned to the user-defined unhandled exception, you'll see the following:
ORA-06510: PL/SQL: unhandled user-defined exception

Tuesday, May 19, 2009

Case Statement

The basic Case statement:
Accept Number1 Prompt '1st Number: '
Accept Number2 Prompt '2nd Number: '

Declare
Var1 Number := &Number1 ;
Var2 Number := &Number2 ;
Begin
Case True
When (Var1 > Var2)
Then Dbms_Output.Put_Line('1st > 2nd');
When (Var1 < Var2)
Then Dbms_Output.Put_Line('1st < 2nd');
Else Dbms_Output.Put_Line('1st = 2nd');
End Case;
End;
/

Boolean Variables

If a boolean variable is not initialized it isn’t true or false. Use NVL to have it take the appropriate course.

Declare
Bool_Var Boolean ;
Begin
If Not Nvl(Bool_Var,False)
Then Dbms_Output.Put_Line('False or Not Initialized');
Else Dbms_Output.Put_Line('True');
End If;
End;
/

Wednesday, April 22, 2009

Enforcing Standards With SubType

Create a new type by adding restrictions to an existing type with 'SubType.'
Create Or Replace Package p_Std
is
SubType City is VARCHAR2(25) ;
SubType State is CHAR(2) not null ;
end;
/
These can now be referenced:
Declare
v_City p_Std.City := 'Pittsburgh';
v_St p_Std.State := 'PA' ;
Begin
Dbms_Output.Put_Line (v_City ||', '||v_St);
End;
/
Pittsburgh, PA

PL/SQL procedure successfully completed.

Tuesday, April 21, 2009

Large Objects

These are my notes from studying the Dummies PL/SQL book on Advanced Datatypes. (In the works are my notes from the Oracle online documentation.)

Storage

8-128Tb of binary/textual data
The environment might restrict to 4Gb

Types

CLOB - internally stored character data
BLOB - internally stored binary data (audio/video)
BFILE - externally stored file

Internal storage: (entire file stored in db)

  • Can be retrieved quickly
  • No concern for individual file management
  • Without a good backup utility, it can take hours/days to do a complete db backup
  • Lob locator, not the data is stored in the table; the actual data is stored elsewhere in the database.
  • External storage: (only file names stored in db)

  • Might be slow if many files in a directory
  • Files might be moved/deleted or changed by user
  • Read-only access
  • Lob locator is stored in a BFILE column
  • See Oracle Database Application Developers Guide - Large Objects for complete details.

    Create Table Lob_Table
    ( Row_Id Number Primary Key
    , File_Name Varchar2(2000)
    , Sample_Cl Clob
    , Sample_Bl Blob
    , Sample_Bf Bfile );


    SQL> desc lob_table
    Name Null? Type
    ------------ -------- ----------------
    ROW_ID NOT NULL NUMBER
    FILE_NAME VARCHAR2(2000)
    SAMPLE_CL CLOB
    SAMPLE_BL BLOB
    SAMPLE_BF BINARY FILE LOB

    Populating BFILE

    To refer to a directory (i.e., c:\data\lob), use the name assigned in 'Create Directory' statement.
    Connect System/??????
    Create Directory LOB_Dir as 'C:\Data\Lob';
    Grant Read, Write On Directory LOB_Dir To Public;
    Connect Scott/Tiger
    Create a pointer to a file and insert into table.
    Declare
    v_Bf Bfile; --stores pointer
    v_Fn Varchar2(100) := 'LOB.TXT'; --stores filename
    Begin
    /*-----------------------------------------------*/
    /* Creates a pointer to the file */
    /*-----------------------------------------------*/
    v_Bf := BFileName ('LOB_DIR', v_Fn);

    /*-----------------------------------------------*/
    /* Inserts a row with the pointer. */
    /*-----------------------------------------------*/
    Insert Into Lob_Table
    ( Row_Id , File_Name , Sample_Bf)
    Values ( 1 , v_Fn , v_Bf );
    End;
    /
    Select Row_Id , File_Name from Lob_Table;


    ROW_ID FILE_NAME
    ---------- ---------------
    1 LOB.TXT


    Load Data To A Clob

    Declare
    v_File_Bf Bfile ;
    v_Sample_Cl Clob ;
    Warning Number ;
    /*-----------------------------------------------*/
    /* You can usually ignore the following four */
    /*-----------------------------------------------*/
    Lang_Ctx Number := Dbms_Lob.Default_Lang_Ctx;
    Charset_Id Number := 0;
    Src_Offset Number := 1;
    Dst_Offset Number := 1;
    Begin
    /*-----------------------------------------------*/
    /* This creates the actual CLOB with a length of */
    /* 0 bytes. */
    /*-----------------------------------------------*/
    Update Lob_Table
    Set Sample_Cl = Empty_Clob()
    Where Row_Id = 1;

    /*-----------------------------------------------*/
    /* Put pointers into the variable Clob and Bfile.*/
    /* -Sample Bfile has a pointer to file 'LOB.TXT' */
    /* -Sample Clob is null, but that can't be used. */
    /*-----------------------------------------------*/
    Select Sample_Bf , Sample_Cl
    Into v_File_Bf , v_Sample_Cl
    From Lob_Table
    Where Row_Id = 1;

    Dbms_Lob.FileOpen
    ( v_File_Bf , Dbms_Lob.File_ReadOnly);

    /*-----------------------------------------------*/
    /* Read the file content into the clob. */
    /*-----------------------------------------------*/
    Dbms_Lob.LoadClobFromFile( v_Sample_Cl
    , v_File_Bf
    , Dbms_Lob.GetLength (v_File_Bf)
    , Src_Offset /* These */
    , Dst_Offset /* are */
    , Charset_Id /* usually */
    , Lang_Ctx /* ignored. */
    , Warning);

    Dbms_Lob.FileClose (v_File_Bf);
    End;
    /

    PL/SQL procedure successfully completed.
  • The variable is a pointer to the real CLOB.
  • Any changes to the CLOB made by using the local pointer go directly to the table -- no 'update' is necessary.

  • Loading a page to a BLOB

    Declare
    v_File_Bf Bfile:= BFileName ('LOB_DIR','TEST.JPG');
    v_Sample_Bl Blob;
    Src_Offset_Nr Number := 1;
    Dst_Offset_Nr Number := 1;
    Begin
    Update Lob_Table
    Set Sample_Bl = Empty_Blob()
    Where Row_Id = 1;

    Select Sample_Bl
    Into v_Sample_Bl
    From Lob_Table
    Where Row_Id = 1;

    Dbms_Lob.FileOpen (v_File_Bf, Dbms_Lob.File_ReadOnly);

    Dbms_Lob.LoadBlobFromFile ( v_Sample_Bl
    , v_File_Bf
    , Dbms_Lob.GetLength(v_File_Bf)
    , Dst_Offset_Nr
    , Src_Offset_Nr ) ;

    Dbms_Lob.FileClose (v_File_Bf);
    End;
    /

    PL/SQL procedure successfully completed.

    Performing basic string operations on CLOBs

    You can use many regular string operations on CLOBs.
  • search for patterns
  • get length
  • get part of the code
  • etc.
  • Declare
    v_Sample_Cl Clob;
    v_Nr Number;
    v_Tx Varchar2 (2000);
    v_Add_Tx Varchar2 (2000) :='Loaded: '
    ||To_Char(Sysdate,'Mm/Dd/Yyyy Hh24:Mi');
    Begin
    Select Sample_Cl
    Into v_Sample_Cl
    From Lob_Table
    Where Row_Id = 1
    For Update;

    /*-----------------------------------------------*/
    /* write at the end */
    /*-----------------------------------------------*/
    Dbms_Lob.WriteAppend ( v_Sample_Cl
    , Length (v_Add_Tx)
    , v_Add_Tx);

    v_Nr := Instr (v_Sample_Cl, 'Loaded:', -1);
    v_Tx := Substr (v_Sample_Cl, v_Nr);

    Dbms_Output.Put_Line (v_Tx);
    End;
    /

    PL/SQL procedure successfully completed.
    Note: A COMMIT could make the LOB pointer invalid.

    Sunday, April 12, 2009

    Handling PL/SQL Errors

    These are my notes from the Oracle documentation on Handling PL/SQL Errors.
    Top

    1 Overview of PL/SQL Runtime Error Handling


    Predefined (by the system) User-defined
    Named Already named (ZERO_DIVIDE) or
    Unnamed, but can be named
    Must be named
    Raised Automatically by the system or with RAISE With RAISE

    Top

    1.1 Guidelines for Avoiding and Handling PL/SQL Errors and Exceptions

    Look for errors here:
  • string manipulation
  • memory error
  • database operations
  • math
  • bad/null input data
  • query returns > 1 row
  • query returns 0 rows
  • disk storage
  • a table was changed and %[ROW]TYPE was not used
  • Decide whether to commit or rollback
  • Always have WHEN OTHERS, but use as a last resort
  • /* Write out debugging information in your exception handlers. You might store such information in a separate table. If so, do it by making a call to a procedure declared with the PRAGMA AUTONOMOUS_TRANSACTION, so that you can commit your debugging information, even if you rollback the work that the main procedure was doing. -- (Investigate & study this) this */
    Top

    2 Advantage of PL/SQL Exceptions

    Handles errors from many statements with a single exception handlerTop

    3 Summary of Predefined PL/SQL Exceptions

    Exception NameSQLCode
    (ORA- if
    different)
    Description
    Access_Into_Null
    -6530
    A program attempts to assign values to the attributes of an uninitialized object.
    Case_Not_Found
    -6592
    No Else and no When works
    Collection_Is_Null
    -6531
    Collection methods other than EXISTS are applied, or values assigned to the elements of an uninitialized nested table or varray.
    Cursor_Already
    _Open
    -6511
    An attempt to open an opened cursor or a cursor in a FOR loop.
    Dup_Val_On_Index
    1
    Attempts to store duplicate values constrained by a unique index.
    Invalid_Cursor
    -1001
    i.e., closing an unopened cursor, etc.
    Invalid_Number
    -1722
    Raised from within SQL.
    (In PL/Sql, Value_Error is raised.) or when a bulk FETCH's LIMIT-clause < 0
    Login_Denied
    -1017
    Invalid username or password
    No_Data_Found
    +100
    (-01403)
    A SELECT INTO returns no rows, or a reference to a nested tables' deleted element or an uninitialized element in an index-by table. This exception is used internally by some SQL functions to signal that they are finished so don't rely on this exception being propagated if you raise it within a function that is called as part of a query.
    Not_Logged_On
    -1012
    A database call issued without being connected to Oracle.
    Program_Error
    -6501
    (contact Oracle support) PL/SQL has an internal problem.
    Rowtype_Mismatch
    -6504
    The host and PL/SQL cursor variables involved in an assignment have incompatible return types. i.e., when a variable is passed to a subprogram.
    Self_Is_Null
    -30625
    A program attempts to call a MEMBER method, but the instance of the object type has not been initialized. The built-in parameter SELF points to the object, and is always the first parameter passed to a MEMBER method.
    Storage_Error
    -6500
    (ran out of memory or memory corrupted)
    Subscript_
    Beyond_Count
    -6533
    A nested table is referenced or index number larger > collection.
    Subscript_
    Outside_Limit
    -6532
    A program references a nested table or varray element using an index number (-1 for example) that is outside the legal range.
    Sys_Invalid
    _Rowid
    -1410
    The character string does not represent a valid rowid.
    Timeout_On_
    Resource
    -51
    A time-out occurs while Oracle is waiting for a resource.
    Too_Many_Rows
    -1422
    A SELECT INTO statement returns more than one row.
    Value_Error
    -6502
    An arithmetic, conversion, truncation, or size-constraint error occurs.
    (For SQL see Invalid_Number)
    Zero_Divide
    -1476
    A program attempts to divide a number by zero.

    Top

    4 Defining Your Own PL/SQL Exceptions

    4.1 Declaring PL/SQL Exceptions

    DECLARE
    Error1 EXCEPTION;
    Top

    4.2 Scope Rules for PL/SQL Exceptions

  • You can declare the same exception in two different blocks.
  • They are local to the block in which they are declared and global to all its sub-blocks
  • The sub-block cannot reference the global exception, unless the exception is declared in a labeled block and you qualify its name with the block label: block_label.exception_name
  • DECLARE
    Error1 EXCEPTION;
    BEGIN
    ---------- sub-block begins--------------------------
    DECLARE
    Error1 EXCEPTION; -- this overrides previous
    BEGIN
    IF 2 < 1 THEN RAISE Error1;
    END IF;
    END;
    ------------- sub-block ends ------------------------
    EXCEPTION
    /*----------------------------------------------*/
    /* Enclosing blocks cannot reference exceptions */
    /* declared in a sub-block */
    /*----------------------------------------------*/

    WHEN Error1
    THEN DBMS_OUTPUT.PUT_LINE('Cannot handle');
    WHEN OTHERS
    THEN DBMS_OUTPUT.PUT_LINE('Error1 not seen');
    END;
    /
    Top

    4.3 Associating a PL/SQL Exception with a Number

    To handle unnamed ORA- messages, use
    PRAGMA EXCEPTION_INIT(defined_exception , -Oracle_error#);
    DECLARE
    Error1 EXCEPTION;
    PRAGMA EXCEPTION_INIT(Error1, -60);
    BEGIN
    -- Some operation that causes an ORA-00060 error
    EXCEPTION
    WHEN Error1 THEN
    -- handle the error
    END;
    /
    Top

    4.4 Defining Your Own Error Messages: Procedure RAISE_APPLICATION_ERROR( -20nnn, message);

    DECLARE
    num_tables NUMBER;
    BEGIN
    SELECT COUNT(*)
    INTO num_tables
    FROM USER_TABLES;

    IF num_tables < 1000 THEN
    Raise_Application_Error(-20101,'Need 1000 tables');
    END IF;
    END;
    /
    DECLARE
    *
    ERROR at line 1:
    ORA-20101: need 1000 tables
    ORA-06512: at line 9
    Top

    4.5 Redeclaring Predefined Exceptions

    -- Don't do it

    Top

    5 How PL/SQL Exceptions Are Raised

    • Implicitly

      • Internal (Ora-)
      • User-defined exceptions set up through EXCEPTION_INIT

    • Explicitly ('RAISE')

      • If user-defined & not associated with an error #
      • If a predefined should process other errors
    Top

    5.1 Raising Exceptions with the RAISE Statement

    DECLARE
    Error1 EXCEPTION;
    BEGIN
    IF 0 < 1 THEN RAISE Error1; /* user-defined */
    END IF;

    IF 7 > 9 THEN RAISE INVALID_NUMBER; /* predefined */
    END IF;
    EXCEPTION
    WHEN Error1
    THEN DBMS_OUTPUT.PUT_LINE('Error 1' );

    WHEN INVALID_NUMBER
    THEN DBMS_OUTPUT.PUT_LINE('Invalid #');
    ROLLBACK;
    END;
    /
    Top

    6 How PL/SQL Exceptions Propagate

    If a handler can't be found in the current block, the exception propagates*, but the enclosing block may not be able to reference the name based on where it was declared.

    *reproduces itself in successive enclosing blocks until a handler is found, and finally to the host environment if no handler found.
    BEGIN
    ---------- sub-block begins-------------------------
    DECLARE
    sub_err EXCEPTION;
    BEGIN
    IF 1 < 2 THEN RAISE sub_err;
    END IF;
    END;
    ------------- sub-block ends------------------------
    EXCEPTION
    WHEN OTHERS THEN ROLLBACK;
    END;
    /
    Top

    7. Reraising* a PL/SQL Exception

    * handle locally & then pass along
    DECLARE
    ErrNum EXCEPTION;
    BEGIN
    ---------- sub-block begins -------------------------
    BEGIN
    IF 1 < 2 THEN RAISE ErrNum; END IF;
    EXCEPTION
    WHEN ErrNum THEN DBMS_Output.Put_Line('Some Stuff');
    /*--------------------------*/
    /* Raise to enclosing block */
    /*--------------------------*/

    RAISE;
    END;
    ------------ sub-block ends -------------------------
    EXCEPTION
    WHEN ErrNum THEN DBMS_Output.Put_Line('More Stuff');
    END;
    /
    Some Stuff
    More Stuff

    PL/SQL procedure successfully completed.
    Top

    8 Handling Raised PL/SQL Exceptions

  • Control does not return to where the exception was raised so processing is not resumed.
  • OTHERS is always the last handler in a block, and handles all exceptions not specifically named.
  • EXCEPTION
    WHEN exception1
    OR exception2
    OR VALUE_ERROR THEN
    -- handle the error
    Top

    8.1 Exceptions Raised in Declarations

    Exceptions raised in a declaration propagates immediately to the enclosing block.

    Top

    8.2 Handling Exceptions Raised in Handlers

    Exceptions raised in a handler propagates immediately to the enclosing block.

    Top

    8.3 Branching to or from an Exception Handler

    You can use a GOTO only from a handler into an enclosing block.

    Top

    8.4 Retrieving the Error Code and Error Message

  • SQLERRM: maximum length is 512 (includes code, message, and table/column name inserts)
  • You cannot use SQLCODE or SQLERRM directly in a SQL statement.
  • Create Table Errors ( Code     Number
    , Message Varchar2(64)
    , Happened Timestamp
    ) ;
    DECLARE
    vSqlCode NUMBER;
    vSqlErrM VARCHAR2(64);
    BEGIN
    some executable statements
    EXCEPTION
    WHEN OTHERS
    THEN vSqlCode := SqlCode;
    vSqlErrM := Substr(SqlErrM, 1 ,64);
    Dbms_Output.Put_Line('Code '||vSqlCode
    || ': ' ||vSqlErrM);
    --??---------------------------------------------------
    --?? Normally we would call another procedure, declared
    --?? with PRAGMA AUTONOMOUS_TRANSACTION, to insert
    --?? information about errors. -- to be investigated
    --??---------------------------------------------------

    Insert Into Errors Values
    ( vSqlCode
    , vSqlErrM
    , SysTimestamp
    );
    End;
    /
    Top

    8.5 Catching Unhandled Exceptions

    Avoid by always coding OTHERS at the topmost level.
    Top

    8.6 Tips for Handling PL/SQL Errors

    To continue with the next statement, place a statement in a sub-block with an exception handler.

    Top
    8.6.1 Continuing after an Exception Is Raised
    The statement 'Print this 2nd' is never reached as an error is implicitly raised by dividing by zero.
    Declare
    Calc Number(8);
    Begin
    Dbms_Output.Put_Line('Print this 1st.');
    Calc := 200/0;
    Dbms_Output.Put_Line('Print this 2nd.');
    Exception
    When Zero_Divide
    Then Dbms_Output.Put_Line('0 Divide Err');
    End;
    /
    Print this 1st.
    0 Divide Err

    PL/SQL procedure successfully completed.
    This is solved by putting an error-prone statement in a sub-block with an exception handler. If it fails, then it passes back out to the enclosing block, which continues with the next statement.
    Declare
    Calc Number(8);
    Begin
    Dbms_Output.Put_Line('Print this 1st.');
    ----- sub-block begins -----------------------------
    Begin
    Calc := 200/0;
    Exception
    When Zero_Divide
    Then Dbms_Output.Put_Line
    ('Handle here then continue');
    End;
    ----- sub-block ends ------------------------------
    Dbms_Output.Put_Line('Print this 2nd.');
    Exception
    When Zero_Divide
    Then Dbms_Output.Put_Line('0 Divide Err');
    End;
    /
    Print this 1st.
    Handle here then continue
    Print this 2nd.

    PL/SQL procedure successfully completed.

    /* ?? You can also perform a sequence of DML operations where some might fail, and process the exceptions only after the entire operation is complete, as described in "Handling FORALL Exceptions with the %BULK_EXCEPTIONS Attribute". -- Investigate */

    Top
    8.6.2 Retrying a Transaction
    1. Encase the transaction in a sub-block.
    2. Place the sub-block inside a For/While loop to limit tries
    3. First, SAVEPOINT.
    4. If transaction...
      1. succeeds: commit and exit loop.
      2. fails: in handler rollback to savepoint, then fix the problem.
    Drop Table Names Purge;
    Create Table Names ( Name Varchar2(8) Primary Key );

    Declare
    vName Varchar2(20) := '&Name';
    vCount Number;
    Begin
    For I In 1..5 Loop /*#2*/
    -- sub-block begins------------------------/*#1*/
    Begin
    Savepoint StartHere; /*#3*/
    /*-----------------------------------------*/
    /* Raises Dup_Val_On_Index Since Same Name */
    /*-----------------------------------------*/

    Insert Into Names Values (vName);
    Commit; /*#4.1*/
    Exit; /*#4.1*/
    Exception
    When Dup_Val_On_Index /*#4.2*/
    Then Rollback To StartHere;
    vCount := vCount + 1;
    vName := vName ||
    To_Char(vCount);
    End;
    -- sub-block ends--------------------------------
    End Loop;
    End;
    /
    Top
    8.6.3 Using Locator Variables to Identify Exception Locations
    If you need to know which statement failed:
    DECLARE
    stmt_no NUMBER;
    name VARCHAR2(100);
    BEGIN
    stmt_no := 1;
    -- some executable statement --
    stmt_no := 2;
    -- some executable statement --
    EXCEPTION
    WHEN NO_DATA_FOUND
    THEN DBMS_OUTPUT.PUT_LINE(
    'Error on '||stmt_no);
    END;
    /
    Top

    9 Overview of PL/SQL Compile-Time Warnings

    See the USER/DBA/ALL_PLSQL_OBJECT_SETTINGS views.
    Top

    9.1 PL/SQL Warning Categories

    SEVERE:
    something could cause unexpected behavior or wrong results
    PERFORMANCE:
    i.e., passing character to number column while inserting
    INFORMATIONAL:
    Things you might want to change (i.e., unreachable code)
    ALL
    Top

    9.2 Controlling PL/SQL Warning Messages

    This parameter can be set
  • at the system level
  • or the session level
  • a single compilation (ALTER PROCEDURE...COMPILE)
  • Alter Session Set PLSql_Warnings='Disable:All';        
    Alter Session Set PLSql_Warnings='Enable:All';
    Alter Session Set PLSql_Warnings='Enable:Performance';
    Alter Session Set PLSql_Warnings='Enable:Severe'
    , 'Disable:Performance'
    , 'Error:06002'; -- PLW-06002 will halt compilation

    Alter Procedure Loc_Var Compile
    PLSql_Warnings='Enable:Performance'
    Reuse Settings; -- Recompile With Extra Checking
    Yes: PL/SQL subprograms
    No: anonymous blocks

    View with SHOW ERRORS or Select * from USER_ERRORS.
    These have a PLW prefix.
    Top

    9.3 Using the DBMS_WARNING Package

    CREATE OR REPLACE PROCEDURE unreachable_code AS
    x Number := 1;
    BEGIN
    IF x = 1 THEN DBMS_OUTPUT.PUT_LINE(1);
    END IF;
    END unreachable_code;
    /
    Procedure created.

    SQL> /*---------------------------------------------------*/
    SQL> /* enable all warning messages for this session */
    SQL> /*---------------------------------------------------*/
    SQL> CALL DBMS_WARNING.set_warning_setting_string(
    'ENABLE:ALL' ,'SESSION');


    Call completed.

    SQL> /*---------------------------------------------------*/
    SQL> /* Check the current warning setting */
    SQL> /*---------------------------------------------------*/
    SQL> SELECT DBMS_WARNING.get_warning_setting_string()
    FROM DUAL;

    DBMS_WARNING.GET_WARNING_SETTING_STRING()
    ---------------------------------------------------------------------------
    ENABLE:ALL


    SQL> /*---------------------------------------------------*/
    SQL> /* Recompile the procedure and a warning about */
    SQL> /* unreachable code displays */
    SQL> /*---------------------------------------------------*/
    SQL> ALTER PROCEDURE unreachable_code COMPILE;

    SP2-0805: Procedure altered with compilation warnings

    SQL> SHOW ERRORS;
    Errors for PROCEDURE UNREACHABLE_CODE:

    LINE/COL ERROR
    -------- -----------------------------------------------------------------
    4/8 PLW-06002: Unreachable code


    SQL> Alter Procedure Unreachable_Code Compile
    Plsql_Warnings = 'Enable:All' Reuse Settings;
    Top

    Saturday, March 21, 2009

    Basic Function

    I'm training for a half-marathon, but have to run a certain number of miles per hour to be able to qualify. Can I do it?

    Two values get passed to the function in the order in which they are presented. The function returns one value to the program.

    The Function:

    Create Or Replace
    Function My_Speed
    (My_Mileage Number
    ,My_Time Number
    )
    Return Number
    Is
    Begin
    Return My_Mileage/My_Time;
    End;
    /

    Calling the Function:

    Begin
    DBMS_Output.Put_Line ('MPH: '
    || My_Speed(&Miles,&Hours)
    );
    End;
    /

    Recompiling Invalid Code

    This is an example of code stored in an invalid state, and how to recompile. The first query shown below was run (and hence stored in User_Source), but compiled with errors.
    Create or Replace Function fHired (vHireDate Date)
    Return Number is
    Begin
    Return Round((Sysdate-vHireDate)/365)
    End;
    /

    Warning: Function created with compilation errors.
    A second piece of code is written that references the first, which is currently invalid. Obviously, even though there are no errors here, this won't compile either.
    Create or Replace Procedure Hired is
    Begin
    For i in (Select EName
    , fHired(HireDate) as Yrs
    From Emp )
    Loop
    DBMS_Output.Put_Line(i.Ename||' '||i.Yrs);
    End Loop;
    End;
    /

    Warning: Procedure created with compilation errors.
    You can display any errors with the "Show Errors [type name]" command. If you don't supply the type, and name, the last compiled code will display its errors.
    SQL> Show Errors
    LINE/COL ERROR
    -------- ----------------------------------------------
    3/28 PL/SQL: ORA-06575: Package or function FHIRED
    is in an invalid state


    SQL> Show Errors Function fHired
    LINE/COL ERROR
    -------- -----------------------------------------------------------------
    5/5 PLS-00103: Encountered the symbol "END" when
    expecting one of the following: ...
    Since both have errors, they are marked as 'Invalid.'

    Select Object_Type
    , Object_Name
    , Status
    From User_Objects
    Where Object_Name in ('FHIRED','HIRED')
    Order By Object_Type
    , Object_Name;


    Object Object
    Type Name Status
    ------------ ------- ------------
    FUNCTION FHIRED INVALID
    PROCEDURE HIRED INVALID
    Next we go back and fix the first piece of code by adding the missing semicolon. When it's run from the prompt, it will show that it got created.
    Create or Replace Function fHired (vHireDate Date)
    Return Number is
    Begin
    Return Round((Sysdate-vHireDate)/365);
    End;
    /

    Function created.
    The second piece of code then can be compiled by issuing the following statement:
    SQL> Alter Procedure Hired Compile;
    Procedure altered.
    This shows the function and procedure as they are stored in the User_Source table.
    Select *
    From User_Source
    Where Name in ('FHIRED','HIRED')
    Order by Name
    , Line;


    NAME TYPE LINE TEXT
    ------ --------- ---- -------------------------------------------------------
    FHIRED FUNCTION 1 Function fHired (vHireDate Date)
    FHIRED FUNCTION 2 Return Number is
    FHIRED FUNCTION 3 Begin
    FHIRED FUNCTION 4 Return Round((Sysdate-vHireDate)/365);
    FHIRED FUNCTION 5 End;

    NAME TYPE LINE TEXT
    ------ --------- ---- -------------------------------------------------------
    HIRED PROCEDURE 1 Procedure Hired is
    HIRED PROCEDURE 2 Begin
    HIRED PROCEDURE 3 For i in (Select EName
    HIRED PROCEDURE 4 , fHired(HireDate) as Yrs
    HIRED PROCEDURE 5 From Emp )
    HIRED PROCEDURE 6 Loop
    HIRED PROCEDURE 7 DBMS_Output.Put_Line(i.Ename||' '||i.Years);
    HIRED PROCEDURE 8 End Loop;
    HIRED PROCEDURE 9 End;

    Wednesday, March 18, 2009

    Function to Capitalize Correctly with 'Mc' or 'Mac'

    Select    EName
    , Mc(EName)
    From Emp
    Where EName Like 'M%'

    ENAME MC(ENAME)
    --------------- ---------------
    MCNEIL McNeil
    MACINTYRE MacIntyre

    MARTIN Martin
    MILLER Miller
    This is a start at capitalizing names correctly. This function will look at the name and capitalize in two parts if it finds either 'MC' or 'MAC' in the first two to three characters.
    Create Or Replace Function Mc(Name Varchar2)
    Return Varchar2
    Is
    Begin
    Return(Case ---------------------------------------
    -- When the name starts with 'MC' or
    -- 'MAC'...
    ---------------------------------------
    When Upper(Substr(Name,1,2))='MC'
    Or Upper(Substr(Name,1,3))='MAC'
    ---------------------------------------
    -- InitCap in two sections. The first
    -- is up to the first 'C' found. The
    -- 2nd is from the first 'C' to the end.
    ---------------------------------------
    Then Initcap
    (Substr
    (Name,1,
    Instr(Upper(Name)
    ,'C'
    ,1)
    )
    )
    || Initcap
    (Substr
    (Name
    ,Instr(Upper(Name)
    ,'C'
    ,1)+1
    )
    )
    ---------------------------------------
    -- If not, 'MC' or 'MAC' InitCap the
    -- whole name.
    ---------------------------------------
    Else Initcap(Name)
    End);
    End;
    /

    Instead of Trigger

    Scott wants to create a view of salary totals by department. He will need to be granted that privilege by System.
    SQL> Connect System/******
    Connected.
    SQL> Grant Create View To Scott;
    Grant succeeded.
    SQL> Connect Scott/Tiger
    Connected.

    Create or Replace View v_TotSal
    As
    Select DeptNo
    ------------------
    , Sum(Sal) As
    SumSal
    ------------------
    From Emp
    Group By DeptNo
    Order By DeptNo;


    View created.

    SQL> Select *
    2 From v_TotSal;

    DEPTNO SUMSAL
    ------ ---------
    10 $9,268
    20 $11,065
    30 $10,055
    If you try to delete one of the rows in this view, you are stopped with an ORA error.
    SQL> Delete From v_TotSal
    2 Where DeptNo = 10;
    Delete From v_TotSal
    *
    ERROR at line 1:
    ORA-01732: data manipulation operation not legal on
    this view
    Since you can't delete directly from that view, you'll need to create a trigger that will delete from the base table based on the input given.
    Create Or Replace Trigger v_TotSal_Del
    Instead Of Delete On v_TotSal
    For Each Row
    Begin
    Delete From Emp
    Where DeptNo = :Old.DeptNo;
    End;
    /


    Trigger created.

    SQL> Delete From v_TotSal
    2 Where DeptNo = 10;

    1 row deleted.
    This time, it was successful.
    SQL> Select Distinct DeptNo
    2 From Emp;

    DEPTNO
    ------
    30
    20

    Trigger: Check Values Before Data Entry

    This trigger is fired whenever the JOB column is inserted or updated on the EMP table.
    Create or Replace Trigger t_Top_Dog
    Before Insert or Update
    Of Job
    On Emp
    For Each Row
    Begin
    If :New.Job = 'PRESIDENT'
    Then Raise_Application_Error
    (-20111,'Only one top dog allowed.');
    End If;
    End;
    /

    Trigger created.

    SQL> Insert Into Emp Values
    2 (7998,'TOBIAS','DEVELOPER',NULL,SYSDATE,3500,NULL,10);

    1 row created.

    SQL> Insert Into Emp Values
    2 (7999,'ELMO','PRESIDENT',NULL,SYSDATE,6000,NULL,20);
    Insert Into Emp Values
    *
    ERROR at line 1:
    ORA-20111: Only one top dog allowed.
    ORA-06512: at "SCOTT.T_TOP_DOG", line 3
    ORA-04088: error during execution of trigger 'SCOTT.T_TOP_DOG'

    Saturday, March 14, 2009

    Explicitly Define Parameters

    Declare
    Procedure pShopList
    (i_Verb Varchar2 :='Buy'
    ,i_Item Varchar2 :=' apples'
    ,i_Priority Varchar2 :=' now' )
    Is
    Begin
    Dbms_Output.Put_Line(i_Verb
    ||i_Item
    ||i_Priority);
    End;
    Begin
    pShopList(i_Item => ' bananas' ); -- 2nd only
    pShopList(i_Priority => ' sometime'); -- 3rd only

    pShopList(i_Priority => ' sometime'
    ,i_Item => ' grapes' ); -- Mix
    End;
    /

    Embedded Function


    Declare
    Function fHired (vHireDate Date)
    Return Number
    Is
    Begin
    Return Round((Sysdate-vHireDate)/365);
    End;

    Begin
    For vRec In (Select Empno, Hiredate
    From Emp)
    Loop
    Dbms_Output.Put_Line
    ('Emp # ' || vRec.Empno
    ||' Hired ' || To_Char(vRec.Hiredate,'Yyyy')
    ||' Years worked: '|| fHired(vRec.Hiredate));
    End Loop;
    End;
    /

    Embedded Procedure

    You can put a print procedure in your code, and call that instead of typing DBMS_Output.Put_Line repeatedly.

    The Code:

    Declare
    vText Varchar2(20) := 'Test Text';

    Procedure pDOPL ( P_String in VARCHAR2 ) is
    Begin
    DBMS_OUTPUT.put_line(p_String);
    End;
    Begin
    pDOPL ('Stmt# 1');
    pDOPL ('Stmt# 2');
    pDOPL (vText);
    End;
    /

    The Output:

    Stmt# 1
    Stmt# 2
    Test Text

    Dbms_Output.Put_Line Spacing

     set serveroutput on feedback off
    begin
    dbms_output.put_line(chr(160)||'Little beginning space') ;
    dbms_output.put_line(chr(9) ||'A lot of beginning space');
    end;
    /

    DBMS_Output.Put_Line in a Procedure

    I get tired of typing 'DBMS_Output.Put_Line,' and unless this turns out to be a big time-waster, I think I'll just call the pPrint procedure:
    pPrint.SQL
    Create Or Replace
    Procedure pPrint (Print_String In Varchar2)
    Is
    Begin
    Dbms_Output.Put_Line(Print_String);
    End;
    /
    This calls the pPrint procedure:
    Begin
    pPrint ('apple');
    End;
    /