Packages

A package is a schema object that can store variables, functions, procedures, constants, and other objects. The purpose of the package is to improve the modularity, performance, and security of data.

The main 4 benefits of using packages

  • Encapsulation / modularity
    • packages allow you encapsulate dependent types or subprograms together to simplify the use
  • Hiding the logic  / information / data / database structure
    • you can easily hide any business logic inside the subprograms
    • you can as well protect data leakage or unauthorized manipulation
    • it is database-structure-change-proof (as any other stored block); meaning, an application does not care about a database structure and whether some table name was changed. This a separation of the application and database layer.
  • Overloading
    • you can define the same procedures but with different parameters (or data types) which will allow you to use the same “process” but in multiple ways
  • Performance
    • during the first run, it is loaded in the memory and therefore no I/O is done with every other use

Every package consists of two parts

  • Specification
  • Body

Specification

The package specification is a mandatory standalone part. In the specification, you declare public procedures, cursors, variables, constants, etc. All that is declared here is and will be publicly accessible from outside of the package. When the package is executed, an instance is created and all components will exist during the instance duration until the end of the session. This part (package specification) interacts with an application.

Body

The package body is a non-mandatory part and contains the definition of public procedures, cursors, etc … defined in the specification. On top of that, it contains private procedures, subprograms, variables and so on, that cannot be called from outside of the package. This is a typical “black box” and it is the place where you can implement your security and information “hiding” logic.

Code sample

Package specification

CREATE OR REPLACE PACKAGE my_schema.pckg_my_sample_package AS
    /*
      %author    OracleWorld
      %purpose   Demonstrate the features of packages

      %desc       ........

      %created   2018-10-28
    */
    
    -- global variables
    g_output_only NUMBER(1) := 0;
    g_debug       NUMBER(1) := 0;
    
    -------------------------------------------------------------------------------
    -- SET_OUTPUT_ONLY - set package global variable
    -------------------------------------------------------------------------------
    PROCEDURE set_output_only(p_output_only IN NUMBER);
    PROCEDURE set_debug(p_debug IN NUMBER);


    /***
     *  Constants
     ***/
    k#ddl_type_run VARCHAR2(15) := 'SOFT';
    k#ddl_object_type VARCHAR2(15) := 'USER';

    -- collections definiton
    TYPE t_data_tab_type IS TABLE OF VARCHAR2(35);
    data_tab t_data_tab_type;

    ------------------------------------------------
    /**
     *  main procedure
     ***/
    PROCEDURE run_all(p_day_run date);
    ------------------------------------------------


    ------------------------------------------------
    -- manual run only!
    PROCEDURE single_run(p_value varchar2);
    ------------------------------------------------
END;

As you can see above, there are several types and definitions inside the specification. Everything that is defined here is publicly accessible. Let’s have a look at a body of such a package.

Package body

CREATE OR REPLACE PACKAGE BODY my_schema.pckg_my_sample_package AS
/****************************************************************************************************************************
    NAME:      PCKG_MY_SAMPLE_PACKAGE
    PURPOSE:   Demonstrate the features of packages
    REVISIONS:
    Ver        Date        Author           Description
    ---------  ----------  ---------------  ---------------------------------------------------------------------------------
    1.0        2018-10-28  OracleWorld      1. Created this package.


****************************************************************************************************************************/
    
    -- local variables
    v_day_to_check date;
    v_step_start date;
    v_step_desc varchar2(100);
    v_error_note varchar2(1000);
    v_run_name varchar2(100) := 'SampleRun_TEST';
    

/***-----------------------------------------------------------------
 *   SET_OUTPUT_ONLY - set package global variable
 ***/
PROCEDURE set_output_only(p_output_only IN NUMBER)
IS

BEGIN

  g_output_only := p_output_only;

END set_output_only;


FUNCTION get_output_only RETURN NUMBER
IS

BEGIN

  RETURN g_output_only;

END get_output_only;
--------------------------------------------------------------------



/***-----------------------------------------------------------------
 *   DEBUG
 ***/
PROCEDURE set_debug(p_debug IN NUMBER)
IS

BEGIN

  g_debug := p_debug;

END set_debug;


FUNCTION get_debug RETURN NUMBER
IS

BEGIN

  RETURN g_debug;

END get_debug;
--------------------------------------------------------------------



/***
 *   PRIVATE PROCEDURE
 ***/
PROCEDURE p_drop_first (
    v_error_code OUT varchar2
    )
IS
    
BEGIN

    
      -- debug only
      IF g_debug = 1 THEN dbms_output.put_line('Getting data ...');  END IF;


        -- get all proxy accesses assigned to regular users (not application accounts -> based on profile selection)
        cmd := q'[SELECT *
                  FROM sample_table]';

      -- debug only
      IF g_debug = 1 THEN dbms_output.put_line('Check if there is ... ');  END IF;

        IF .....
        THEN
            RETURN;
        END IF;
        
      -- debug only
      IF g_debug = 1 THEN dbms_output.put_line('Some found .. proceeding');  END IF;

        FOR i IN -- some range
        LOOP
             -- do something here
        END LOOP;

    v_sql_rowcount := sql%rowcount;

    commit;

    EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        v_error_code := sqlcode ||';'|| sqlerrm;
        v_error_note := 'Unexpected error';
        


END p_drop_first;


/***
 *  PRIVATE PROCEDURE
 ***/
PROCEDURE p_process_data(p_user_name VARCHAR2) IS
BEGIN

     -- do something here

END p_process_data;




/****
 *
 *  main RUN CONTROL procedure
 *
 ***/
PROCEDURE run_all (p_day_run date)
IS

  v_error_code varchar2(1000);
  v_ex_custom EXCEPTION;
  v_ex_already_loaded EXCEPTION;

BEGIN

    -- check output setting
    IF get_output_only() = 1
      THEN dbms_output.put_line('Debug and output ON');
      ELSE dbms_output.put_line('Debug and output OFF');
    END IF;

    v_step_start := sysdate;
    v_day_to_check := p_day_run;

   
    p_drop_first (v_error_code);
    IF v_error_code IS NOT NULL THEN RAISE v_ex_custom; END IF;


    -- for ALL data in the ??? table
    FOR dat_block IN (SELECT DISTINCT column_name  as my_col
                      FROM  my_table t
                      WHERE  t.col_data IN (SELECT another_data FROM another_table)                       
                    )
      LOOP
          IF dat_block.my_col != 'DBA_DATA'
          THEN
              -- check output setting
              IF g_debug = 1
                THEN 
                  dbms_output.put_line(' ');
                  dbms_output.put_line('----------------------------------------------');
                  dbms_output.put_line('Data: '||dat_block.my_col);
              END IF;

                -- start processing
                p_process_data(dat_block.my_col);
          END IF;
      END LOOP;
      
      

    EXCEPTION

      WHEN v_ex_custom THEN
          v_error_note := 'ERROR - Custom exception';
          dbms_output.put_line(v_error_note);
          
      WHEN v_ex_already_loaded THEN
          v_error_note := 'ERROR - Date already loaded - aborting script';   
          dbms_output.put_line(v_error_note);
          
      WHEN OTHERS THEN
          ROLLBACK;
          v_error_code := sqlcode ||';'|| sqlerrm;
          v_error_note := 'Unexpected error';
          dbms_output.put_line(v_error_code||' >> '||v_error_note);
         
    -- finish

END run_all;


/****
 *
 *  MANUAL ONLY !!!
 *    check single value only
 *
 ****/
PROCEDURE single_run (p_value varchar2)
IS

  v_error_code varchar2(1000);
  v_ex_no_user EXCEPTION;
  v_ex_custom EXCEPTION;
  v_ex_already_loaded EXCEPTION;

BEGIN

    -- check output setting
    IF g_debug = 1
      THEN dbms_output.put_line('Debug and output ON');
      ELSE dbms_output.put_line('Debug and output OFF');
    END IF;

    IF nvl(p_value, '') = '' THEN
      RAISE v_ex_no_value;
    END IF;

    v_step_start := sysdate;
    v_day_to_check := v_step_start;



    p_drop_first (v_error_code);
    IF v_error_code IS NOT NULL THEN RAISE v_ex_custom; END IF;


    -- for ALL data in the ??? table
    FOR dat_block IN (SELECT DISTINCT column_name  as my_col
                      FROM  my_table t
                      WHERE 1=1
                      AND t.data_val = p_value  -- run for a particular value only
                      AND t.col_data IN (SELECT another_data FROM another_table)                       
                    )
      LOOP
          IF dat_block.my_col != 'DBA_DATA'
          THEN
              -- check output setting
              IF g_debug = 1
                THEN 
                  dbms_output.put_line(' ');
                  dbms_output.put_line('----------------------------------------------');
                  dbms_output.put_line('Data: '||dat_block.my_col);
              END IF;

                -- start processing
                p_process_data(dat_block.my_col);
          END IF;
      END LOOP;




    EXCEPTION

      WHEN v_ex_custom THEN
          v_error_note := 'ERROR - Custom exception';
          dbms_output.put_line(v_error_note);
          
      WHEN v_ex_no_value THEN
          v_error_note := 'ERROR - No value provided';
          dbms_output.put_line(v_error_note);
          
      WHEN v_ex_already_loaded THEN
          v_error_note := 'ERROR - Date already loaded - aborting script';  
          dbms_output.put_line(v_error_note);
         
      WHEN OTHERS THEN
          ROLLBACK;
          v_error_code := sqlcode ||';'|| sqlerrm;
          v_error_note := 'Unexpected error';
          dbms_output.put_line(v_error_code||' >> '||v_error_note);
          

    -- finish

END single_run;

END;

Here you can see how some procedures are hidden inside the package; they are private. You can also overload them by changing the input parameters or their data types.

Overloading

CREATE PACKAGE pckg_load_emps AS
   PROCEDURE p_store_employee (id_employee NUMBER, name_full VARCHAR2, salary NUMBER);
   PROCEDURE p_store_employee (id_employee VARCHAR2, name_full VARCHAR2, salary INTEGER);
   PROCEDURE p_store_employee (id_employee INTEGER, name_full VARCHAR2);
END pckg_load_emps;


CREATE PACKAGE BODY pckg_load_emps AS
   PROCEDURE p_store_employee (id_employee NUMBER, name_full VARCHAR2, salary NUMBER) IS
   BEGIN
      INSERT INTO employee
         VALUES (id_employee, name_full, trunc(salary));
   END p_store_employee;

   PROCEDURE p_store_employee (id_employee VARCHAR2, name_full VARCHAR2, salary INTEGER) IS
   BEGIN
      INSERT INTO employee
         VALUES (regexp_replace(id_employee, '[^[:digit:]]', ''), name_full, salary);
   END p_store_employee;

   PROCEDURE p_store_employee (id_employee INTEGER, name_full VARCHAR2) IS
   BEGIN
      INSERT INTO employee
         VALUES (id_employee, name_full, 0);
   END p_store_employee;
END pckg_load_emps;

Now, when you have your package created, you can easily call it as follows:

BEGIN

  my_schema.pckg_my_sample_package.set_output_only(1);
  my_schema.pckg_my_sample_package.set_debug(1);

  my_schema.pckg_my_sample_package.single_run('Sample');

END;