PL/SQL by Example / Procedures and Functions
Named blocks include procedures and functions. These can be called repeatedly. The declarative section within both types of methods is indicated by the keyword IS, which is mandatory. Further division into the executable part and the exception handling part is identical to anonymous blocks.
Procedure
DECLARE
/* */ -- empty comment just for visual separation of internal methods
PROCEDURE mp_PrintCurrentYear
IS
l_Year CHAR(4):=TO_CHAR(SYSDATE, 'YYYY'); -- local variable
BEGIN
dbms_output.put_line(l_Year); -- 2016
END;
/* */
BEGIN
-- calling a parameterless procedure
mp_PrintCurrentYear;
-- reusing the procedure
mp_PrintCurrentYear;
END;
Function
The main difference between a procedure and a function is the return value. A procedure only executes the given logic (it can be compared to void methods in other languages), while a function additionally returns a value of a predefined type.
DECLARE
l_CurrentYear CHAR(4);
/* */
FUNCTION mf_ReturnCurrentYear
RETURN CHAR -- return value
IS
BEGIN
RETURN TO_CHAR(SYSDATE, 'YYYY');
END;
/* */
BEGIN
-- assigning the result of the parameterless function to a variable
l_CurrentYear:=mf_ReturnCurrentYear;
dbms_output.put_line(l_CurrentYear); -- 2016
END;
Parameters
Input
Both procedures and functions can be generalized by adding input parameters. This allows them to execute the same code, but with different data each time.
DECLARE
l_Result NUMBER;
/* */
FUNCTION mf_Multiply(
p_A NUMBER -- input parameters
, p_B NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN (p_A*p_B);
END;
/* */
BEGIN
-- passing parameters to the function call
-- and storing the result in a variable
l_Result:=mf_Multiply(
p_A => 10
, p_B => 2
);
dbms_output.put_line(l_Result); -- 20
END;
When calling methods, it is not necessary to explicitly name the parameters. You can rely on the order in the definition, but if this changes, the program will most likely stop working. It is also possible to define a default value for an input parameter using the assignment operator.
DECLARE
l_Result NUMBER;
/* */
FUNCTION mf_Power(
p_Base NUMBER
, p_Exponent NUMBER:=2 -- default value
) RETURN NUMBER
IS
BEGIN
RETURN (POWER(p_Base,p_Exponent)); -- Oracle power function
END;
/* */
BEGIN
-- sequential parameter passing
l_Result:=mf_Power(8, 3);
dbms_output.put_line(l_Result); -- 512
-- return value of a function as an input parameter of another function
-- and omitting the second parameter (using the default value)
dbms_output.put_line(mf_Power(8)); -- 64
END;
Output
The procedural language PL/SQL allows defining so-called output parameters, which largely blur the difference between a procedure and a function. They are used wherever a single return value is not sufficient.
DECLARE
l_Day CHAR(2);
l_Month CHAR(2);
l_Year CHAR(4);
/* */
PROCEDURE mp_SplitDate(
p_Date DATE
, o_Day OUT CHAR
, o_Month OUT CHAR
, o_Year OUT CHAR
)
IS
BEGIN
-- assignment to OUT parameters is allowed
o_Day:=TO_CHAR(p_Date, 'DD');
o_Month:=TO_CHAR(p_Date, 'MM');
o_Year:=TO_CHAR(p_Date, 'YYYY');
END;
/* */
BEGIN
mp_SplitDate(
p_Date => SYSDATE -- 5.4.2016
, o_Day => l_Day
, o_Month => l_Month
, o_Year => l_Year
);
dbms_output.put_line(l_Day||'/'||l_Year||'/'||l_Month); -- 05/2016/04
END;
Purely for clarity, I recommend using a function if we require returning a single value, and for two or more values, use a procedure with OUT parameters.
DECLARE
l_Day CHAR(2);
l_Month CHAR(2);
l_Year CHAR(4);
/* */
FUNCTION mf_SplitDate(
p_Date DATE
, o_Day OUT CHAR
, o_Month OUT CHAR
) RETURN CHAR
IS
BEGIN
o_Day:=TO_CHAR(p_Date, 'DD');
o_Month:=TO_CHAR(p_Date, 'MM');
RETURN TO_CHAR(p_Date, 'YYYY'); -- year as return value
END;
/* */
BEGIN
-- possible, but unclear and illogical
l_Year:=mf_SplitDate(
p_Date => SYSDATE -- 5.4.2016
, o_Day => l_Day
, o_Month => l_Month
);
dbms_output.put_line(l_Day||'/'||l_Year||'/'||l_Month); -- 05/2016/04
END;
Input/Output
By combining the previous two types, we get the IN OUT parameter. The variable passed to such a parameter internally copies its value into the method, and after processing is complete, copies the resulting value back out to the variable. Forcing the passing of only a memory reference (thus preventing the “copying” of the variable’s value) is facilitated by the NOCOPY clause before the data type definition.
DECLARE
l_Date DATE:=TO_DATE('01.01.2016', 'DD.MM.YYYY');
/* */
PROCEDURE mp_AddMonth(
b_Date IN OUT DATE -- binary parameter
)
IS
BEGIN
b_Date:=ADD_MONTHS(b_Date, 1);
END;
/* */
BEGIN
mp_AddMonth(l_Date);
dbms_output.put_line(TO_CHAR(l_Date, 'DD.MM.YYYY')); -- 01.02.2016
END;
Overloading
Similar to other languages, methods in PL/SQL can also be overloaded, i.e., defining several with the same name differing only in the number or data type of parameters.
DECLARE
/* */
FUNCTION mf_ReturnPerimeter(
p_A NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN (p_A*4);
END;
/* */
FUNCTION mf_ReturnPerimeter(
p_A NUMBER
, p_B NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN ((p_A+p_B)*2);
END;
/* */
BEGIN
-- square
dbms_output.put_line(mf_ReturnPerimeter(10)); -- 40
-- rectangle
dbms_output.put_line(mf_ReturnPerimeter(10, 5)); -- 30
END;
Stored procedures
For larger applications, I recommend concentrating most of the business logic in packages (more in one of the next parts). However, stored procedures and functions can still be encountered in many, predominantly older, projects, so it is advisable to mention them at least briefly.
CREATE OR REPLACE FUNCTION sf_ReturnCurrentYear RETURN CHAR AS BEGIN RETURN TO_CHAR(SYSDATE, 'YYYY'); END; /
SELECT sf_ReturnCurrentYear FROM dual ; -- 2016