After contributing few articles on Microsoft SQL Server, I thought it would be a good time to do something Oracle specific. This first Oracle article of mine describes some basic tasks but extends to more advanced features.
So the purpose of this article is two-fold: To create a basis for a toolkit collection for developers and administrators to extend and use and also to describe some other techniques how Oracle functionality can be extended. The main areas covered are:
- Creation of a package
- Defining constants and publishing them
- Creating deterministic functions
- Creating a function that returns a result set
- Creating a pipelined function
Creating the Schema for Utilities
The first thing is to create a new schema for the utility package. I decided to create a new schema because of few reasons:
- Security: The owner of the utilities can be granted such permissions which wouldn’t normally be granted to end-users.
- Administration: When all the utilities are in a single, separated schema, administrative tasks such as schema compilations, exports etc. become easier.
Creating the user is basically simple. In your installation, just check that the default and temporary tablespaces are correct. However, since this new user will be granted privileges to
sys user’s tables,
SYSDBA privileges are required.
CREATE USER ToolkitUser
IDENTIFIED BY Hammer
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP;
GRANT CREATE SESSION TO ToolkitUser;
GRANT RESOURCE TO ToolkitUser;
GRANT SELECT ON sys.v_$session TO ToolkitUser;
Creating the Package
The downloadable scripts include the package header and body in a single file along with the types needed in this example. For convenience reasons, a public synonym is created for the package at the end of the script. The public synonym guarantees that the package can be called without specifying the schema as long as necessary privileges are granted to the caller.
The package contains several simple examples of functions just to get to know the basics. These functions won’t be discussed in more detail but few things could be pointed out.
- Even though some of the functions are just short ‘aliases’ to native Oracle functions, I wanted to create a bit more intuitive versions.
- Most of the functions are defined as
DETERMINISTIC meaning that the function always produces the same result if the input values are the same. So these functions can be used in queries, but also in indexes.
The simple functions included are listed below. I decided to create mostly date-related functions since they often seem to cause headache.
Day: Returns the number of the day for the given date.
Month: Returns the number of the month for the given date.
Year: Returns the number of the month for the given date.
FirstDayOfMonth: Returns the first day of month for the given date.
FirstDayOfYear: Returns the first day of year for the given date.
LastDayOfMonth: Returns the last day of month for the given date.
DaysInMonth: Returns the number of days in month for the given date.
DaysLeftInYear: Returns the number of days left in year for the given date.
WorkingDaysLeftInYear: Returns the number of working days left in the year for the given date.
Both Holiday and Toolkit-package scripts are meant to be run under ToolkitUser so after creating the user, get a new connection using ToolkitUser's credentials and execute the scripts. If you use SQL*Plus (or equivalent), add the necessary slashes (
/) at the end of package header and body.
Constants can be added to the package header and then be used throughout the package. However, a constant cannot be referenced in a SQL statement from outside the package. In the Toolkit-package, I’ve defined a constant for PI and I could use it inside a function or a stored procedure, but in order to use it I have to create a small wrapper function for the constant. In this example, the
PI_CONSTANT is published using the
The constant declaration is:
PI_CONSTANT CONSTANT NUMBER := 3.14159265358979323846264338327950288419;
and the publishing function:
FUNCTION Pi RETURN NUMBER DETERMINISTIC IS
RETURN ( PI_CONSTANT );
Now I can use the value of the PI to calculate for example the area of a 30 centimeter pizza:
SELECT Toolkit.PI * POWER(30/2, 2) FROM Dual;
Security, AUTHID DEFINER Example
Since the package can be defined to run either using callers or definers privileges, it can be used to encapsulate operations which otherwise wouldn’t be allowed to the caller. This is the same situation as with stand-alone procedures and functions.
Let’s create a small function that returns
1 if a transaction is active for the caller. In order to do this, we’ll need access to
v$session to get the address of the transaction state object (if any). Earlier in this article, when the user was created, it was granted
SELECT access to
sys.v_$session. This is the actual dynamic view containing the information about sessions. Now, since the user owning the package (
ToolkitUser) has access to the view, we can define the function in the package as:
FUNCTION TransactionActive RETURN NUMBER IS
SELECT DECODE( vs.TADDR, NULL, 0, 1)
FROM v$session vs
WHERE vs.SID = SYS_CONTEXT('USERENV', 'SID');
RETURN ( nActive );
NULL, then there’s currently no transaction. In the function, we need to know from what session we’re querying the state so we need to get the
SID for the caller. This is done using
The package itself was created using definers rights:
CREATE OR REPLACE PACKAGE Toolkit
Now the caller doesn’t have to have access to
sys.v_$session, just to the
Toolkit –package in order to know if there’s an active transaction for the session. To get the result, just try:
SELECT Toolkit.TransactionActive FROM Dual;
Function Returning a Result Set
Functions can also be used to return result sets. To test this, let’s create a function that returns the numbers between given limits. First, we have to define a type for a single row and then a table type containing the data. Both of these statements are included in the package script.
-- Type and table for number values
CREATE OR REPLACE TYPE numberRow AS OBJECT (
CREATE OR REPLACE TYPE numberTable IS TABLE OF numberRow;
After these are defined, we can create the actual function in the package:
FUNCTION NumbersBetween(startValue NUMBER, endValue NUMBER ) RETURN numberTable IS
tabNumbers numberTable := numberTable();
FOR nCounter IN startValue..endValue LOOP
tabNumbers(tabNumbers.COUNT) := numberRow(nCounter);
The function uses a
FOR loop to generate the numbers. In every iteration, the number table is extended by 1 element and a new row containing the number is added to the table. When the function exits, the table of numbers is returned to the caller.
In order to use the function,
TABLE statement has to be used. So getting for example numbers between 1 and 5, the statement would be:
SELECT * FROM TABLE( Toolkit.NumbersBetween(1, 5));
And the result is:
Creating a Pipelined Function
Pipelined function also returns result sets much like we saw in the previous example. The difference is that a pipelined function doesn’t return the whole set at the end of the execution of the function. In a pipelined function,
PIPE ROW statement is used to send the result to the caller immediately. The concept is quite similar to
yield return structure in C#.
Let’s create the same
NumbersBetween –function, but this time pipelined. The package has a table type definition which is used as the return type for the pipelined function. The type is named
numberTable2 so that it won’t get mixed with the previously defined type.
TYPE numberTable2 IS TABLE OF NUMBER;
Now the function looks like:
FUNCTION NumbersBetweenPiped(startValue NUMBER, endValue NUMBER ) RETURN numberTable2
FOR nCounter IN startValue..endValue LOOP
PIPE ROW ( nCounter );
The idea is the same as before, but this time we’re not gathering the result to a single set and returning it. Instead, each value is returned separately at the
PIPE ROW statement. Using the function looks exactly the same as before:
SELECT * FROM TABLE( Toolkit.NumbersBetweenPiped(1, 5) );
One big advantage of using pipelined functions is that the result set doesn’t have to be stored until it's returned because the results are returned to the caller as they ready. This will have a positive memory impact and also may improve performance for example because parallel operations can be done earlier.
Other Examples of Pipelined Functions
To demonstrate few more examples for functions returning result sets, I’ve included the following (pipelined) functions to the package.
The function is much like the
NumbersBetween –function but operates on dates. The parameters are in different order, first the end date and then the start date. This is because the start date is optional. So, if the start date is not defined in the call,
SYSDATE is used. The function implementation is:
FUNCTION DatesUntil(endDate DATE, startDate DATE DEFAULT SYSDATE ) RETURN dateTable
dCounter DATE := TRUNC( startDate );
WHILE TRUNC( endDate ) >= dCounter LOOP
PIPE ROW( dCounter );
dCounter := dCounter + 1;
Getting for example this day and the next 5 days would look like:
SELECT * FROM TABLE( Toolkit.DatesUntil( SYSDATE + 5 ) );
And the results:
Actually, this isn't a pipelined function since it returns only one number. However, a pipelined function is used to demonstrate different usage possibilities. The scripts contains a table called
and the package has a function called
. The idea of the function is that it calculates how many working dates are left in the year for the given date (defaults to current date). Holidays are excluded based on the information in
Holiday -table is created as follows (note that in the scripts the
Holiday -table is created before the package in order to prevent compilation errors).
CREATE TABLE ToolkitUser.Holiday (
Holiday DATE NOT NULL PRIMARY KEY,
CREATE PUBLIC SYNONYM Holiday FOR ToolkitUser.Holiday;
Now, we can use the
DatesUntil –function to populate the
Holiday-table. Let’s add some weekends to holidays.
INSERT INTO ToolkitUser.Holiday (Holiday, Description)
TO_CHAR(days.Column_Value, 'DAY' )
FROM TABLE( Toolkit.DatesUntil( SYSDATE + 100)) days
WHERE TO_CHAR(days.Column_Value, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') IN ('SAT','SUN');
Now, we can select the amount of working days left this year (2011) excluding weekends:
SELECT Toolkit.WorkingDaysLeftInYear FROM Dual;
One more example is a splitter function which receives a
string and a delimiter for values. It breaks the
string into parts based on the delimiter and returns each element. The functionality is much like .NET Framework's
String.Split method. The implementation:
FUNCTION StringSplit(string VARCHAR2, delimiter VARCHAR2 DEFAULT ';') RETURN stringTable
nStartPosition NUMBER := 1;
FOR endPosition IN (SELECT Column_Value
FROM TABLE( StringOccurences( string, delimiter ))) LOOP
PIPE ROW( SUBSTR( string, nStartPosition, endposition.Column_Value-nStartPosition));
nStartPosition := endposition.Column_Value + 1;
PIPE ROW( SUBSTR( string, nStartPosition ));
This function uses another pipelined function,
StringOccurences, which returns all the indexes from the original
string where the delimiter is found and then based on those results, this function splits the
string. Calling the function:
SELECT NVL(Column_Value, ’— NULL –’) AS Result
FROM TABLE(Toolkit.StringSplit( ’;Sunday;Afternoon;’, ’;’));
- NULL -
- NULL –
Hopefully this article has demonstrated some techniques to create packaged functions in Oracle environment. I’d be grateful if you would have the extra time for comments and votes. Thank you.
- March 13, 2011: Article created.
- April 1, 2012: Slight modification
+ examples added to scripts.