PL/SQL by Example / Package

In previous parts, most examples were demonstrated at the level of a single anonymous PL/SQL block. In practice, however, it is more appropriate to unify functionality into logical units, called packages. The advantage is not only the grouping of related objects and thus clearer and more readable code, but also higher performance (the package is kept in memory for the duration of the session), simpler administration of access rights, and the ability to shield internal logic from other objects in the database.

A package consists of two parts. The mandatory part is the specification (sometimes also called the “header”) containing only the list and definition of objects, the optional part is the body, which further describes the objects and their function. The rule is that what is declared in the header is publicly visible, while what is declared only in the package body is private.

CREATE OR REPLACE PACKAGE MyPackage
AS
  -- public declarative part
  g_Public NUMBER:=10;
END;
/
CREATE OR REPLACE PACKAGE BODY MyPackage
AS
  -- private declarative part
  g_Private NUMBER:=20;
  G_CONSTANT CONSTANT NUMBER:=30;
END;
/
BEGIN
  dbms_output.put_line(MyPackage.g_Public); -- 10
  MyPackage.g_Public:=30;
  dbms_output.put_line(MyPackage.g_Public); -- 30
  -- CANNOT MyPackage.g_Private:=40;
  -- CANNOT dbms_output.put_line(MyPackage.g_Private);
END;

It is possible to wrap not only variables and constants in a package, but also types, procedures, functions, cursors, and user-defined exceptions.

CREATE OR REPLACE PACKAGE MyPackage
AS
  -- public type
  TYPE Varchar_TT IS TABLE OF VARCHAR2(4000) INDEX BY PLS_INTEGER;

  -- public function specification
  FUNCTION mf_ArrayToString(
      pt_Array MyPackage.Varchar_TT
    , p_Delimiter CHAR:=', '
  ) RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY MyPackage
AS
  -- public function body
  FUNCTION mf_ArrayToString(
      pt_Array MyPackage.Varchar_TT
    , p_Delimiter CHAR:=', '
  ) RETURN VARCHAR2
  IS
    l_Result VARCHAR2(4000);
  BEGIN
    FOR i IN 1..pt_Array.COUNT LOOP
      l_Result:=l_Result||p_Delimiter||pt_Array(i);
    END LOOP;
    RETURN LTRIM(l_Result, p_Delimiter);
  END;
END;
/
DECLARE
  lt_Array MyPackage.Varchar_TT;
BEGIN
  lt_Array(1):='one';
  lt_Array(2):='two';
  dbms_output.put_line(MyPackage.mf_ArrayToString(lt_Array)); -- one, two
END;

If we refer to an internal method inside the package, it is important that its definition physically precedes the definition of the method from which we are calling it. The difference in the specification and use of a public versus an internal method is illustrated in the following example.

CREATE OR REPLACE PACKAGE MyPackage
AS
  -- public parameterless function specification
  FUNCTION mf_ReturnPrivate
    RETURN NUMBER;

  -- public parameterless procedure specification
  PROCEDURE mp_SetPrivateToHundred;
END;
/
CREATE OR REPLACE PACKAGE BODY MyPackage
AS
  -- private variable
  g_Private NUMBER;

  -- private procedure
  PROCEDURE mpi_SetPrivate(
      p_Parameter NUMBER
  )
  IS
  BEGIN
    g_Private:=p_Parameter;
  END;

  -- public parameterless function body
  FUNCTION mf_ReturnPrivate
    RETURN NUMBER
  IS
  BEGIN
    RETURN g_Private;
  END;

  -- public parameterless procedure body
  PROCEDURE mp_SetPrivateToHundred
  IS
  BEGIN
    -- call to private procedure
    mpi_SetPrivate(100);
  END;
END;
/
BEGIN
  -- CANNOT MyPackage.mpi_SetPrivate(100);
  MyPackage.mp_SetPrivateToHundred;
  dbms_output.put_line(MyPackage.mf_ReturnPrivate); -- 100
END;

Just like stored procedures from the previous chapter, functions in packages can also be used in SQL queries.

SELECT MyPackage.mf_ReturnPrivate
  FROM dual
;
-- 100

Leave a reply

Your email address will not be published. Required fields are marked *