There have been several cases where I needed to return a list of records after performing complex processing in Oracle.
I found out that the preferred method to do it is by using PIPELINED PL/SQL functions.
The last time I have done this was a few weeks ago where I had to “dig” into a log of user actions and extract information about user’s activity on a daily basis.
Other cases where I have used the same technique in the past were involving:
In all these cases I needed to query several tables and use temporary tables to collect intermediate results.
An example of the use of PIPELINED functions can be found here.
Here I summarize the steps must be taken when I have to implement something similar.
1. Create an Object Type with the necessary fields.
CREATE OR REPLACE TYPE TYP_DAILY_USER_TOTALS AS OBJECT (
user_nm VARCHAR2 (16),
act_dt DATE,
tot_proc NUMBER (6),
tot_in_progress NUMBER (6),
tot_cancelled NUMBER (6),
tot_failed NUMBER (6),
tot_compl NUMBER (6),
avg_assign_tm NUMBER (6),
median_assign_tm NUMBER (6),
start_tm DATE,
end_tm DATE
);
2. Create a collection
CREATE OR REPLACE TYPE TYP_DAILY_USER_TOTALS_COL AS TABLE OF typ_daily_user_totals;
3. Implement a function returning a collection
CREATE OR REPLACE FUNCTION get_daily_user_totals(v_user_nm varchar2)
RETURN typ_daily_user_totals_col PIPELINED
AS
PRAGMA AUTONOMOUS_TRANSACTION;
TYPE daily_totals_rec IS RECORD (
act_dt DATE,
tot_proc NUMBER (6),
tot_in_progress NUMBER (6),
tot_cancelled NUMBER (6),
tot_failed NUMBER (6),
tot_compl NUMBER (6),
avg_assign_tm NUMBER (6),
median_assign_tm NUMBER (6),
start_tm DATE,
end_tm DATE
);
TYPE daily_totals_tbl IS TABLE OF daily_totals_rec;
daily_totals daily_totals_tbl;
BEGIN
/*
Do whatever is needed to collect your results, i.e. in a global temporary table
*/
. . . . . . .
/*
Collect your results in a PL/SQL table
*/
SELECT act_dt,
tot_proc,
tot_in_progress,
tot_cancelled,
tot_failed,
tot_compl,
avg_assign_tm,
median_assign_tm,
start_tm,
end_tm
BULK COLLECT INTO daily_totals
FROM g_daily_user_totals;
/*
Commit or rollback to clean up the temporary tables and close the autonomous transaction
*/
ROLLBACK;
/*
Loop to pipe collected results
*/
FOR i IN 1 .. daily_totals.COUNT ()
LOOP
PIPE ROW (NEW typ_daily_user_totals (
v_user_nm,
daily_totals (i).act_dt,
daily_totals (i).tot_proc,
daily_totals (i).tot_in_progress,
daily_totals (i).tot_cancelled,
daily_totals (i).tot_failed,
daily_totals (i).tot_compl,
daily_totals (i).avg_assign_tm,
daily_totals (i).median_assign_tm,
daily_totals (i).start_tm,
daily_totals (i).end_tm));
END LOOP;
RETURN;
END;
4. Select from the function
SELECT user_nm, act_dt, tot_proc, tot_in_progress, tot_cancelled, tot_failed, tot_compl,
avg_assign_tm, median_assign_tm, start_tm, end_tm
FROM TABLE (get_daily_user_totals(:aUser));
Some notes on the above:

I found out that the preferred method to do it is by using PIPELINED PL/SQL functions.
The last time I have done this was a few weeks ago where I had to “dig” into a log of user actions and extract information about user’s activity on a daily basis.
Other cases where I have used the same technique in the past were involving:
- Complex validations and return of validation results (warnings, errors, etc.).
- Tree walking and joining with other tables.
In all these cases I needed to query several tables and use temporary tables to collect intermediate results.
An example of the use of PIPELINED functions can be found here.
Here I summarize the steps must be taken when I have to implement something similar.
1. Create an Object Type with the necessary fields.
CREATE OR REPLACE TYPE TYP_DAILY_USER_TOTALS AS OBJECT (
user_nm VARCHAR2 (16),
act_dt DATE,
tot_proc NUMBER (6),
tot_in_progress NUMBER (6),
tot_cancelled NUMBER (6),
tot_failed NUMBER (6),
tot_compl NUMBER (6),
avg_assign_tm NUMBER (6),
median_assign_tm NUMBER (6),
start_tm DATE,
end_tm DATE
);
2. Create a collection
CREATE OR REPLACE TYPE TYP_DAILY_USER_TOTALS_COL AS TABLE OF typ_daily_user_totals;
3. Implement a function returning a collection
CREATE OR REPLACE FUNCTION get_daily_user_totals(v_user_nm varchar2)
RETURN typ_daily_user_totals_col PIPELINED
AS
PRAGMA AUTONOMOUS_TRANSACTION;
TYPE daily_totals_rec IS RECORD (
act_dt DATE,
tot_proc NUMBER (6),
tot_in_progress NUMBER (6),
tot_cancelled NUMBER (6),
tot_failed NUMBER (6),
tot_compl NUMBER (6),
avg_assign_tm NUMBER (6),
median_assign_tm NUMBER (6),
start_tm DATE,
end_tm DATE
);
TYPE daily_totals_tbl IS TABLE OF daily_totals_rec;
daily_totals daily_totals_tbl;
BEGIN
/*
Do whatever is needed to collect your results, i.e. in a global temporary table
*/
. . . . . . .
/*
Collect your results in a PL/SQL table
*/
SELECT act_dt,
tot_proc,
tot_in_progress,
tot_cancelled,
tot_failed,
tot_compl,
avg_assign_tm,
median_assign_tm,
start_tm,
end_tm
BULK COLLECT INTO daily_totals
FROM g_daily_user_totals;
/*
Commit or rollback to clean up the temporary tables and close the autonomous transaction
*/
ROLLBACK;
/*
Loop to pipe collected results
*/
FOR i IN 1 .. daily_totals.COUNT ()
LOOP
PIPE ROW (NEW typ_daily_user_totals (
v_user_nm,
daily_totals (i).act_dt,
daily_totals (i).tot_proc,
daily_totals (i).tot_in_progress,
daily_totals (i).tot_cancelled,
daily_totals (i).tot_failed,
daily_totals (i).tot_compl,
daily_totals (i).avg_assign_tm,
daily_totals (i).median_assign_tm,
daily_totals (i).start_tm,
daily_totals (i).end_tm));
END LOOP;
RETURN;
END;
4. Select from the function
SELECT user_nm, act_dt, tot_proc, tot_in_progress, tot_cancelled, tot_failed, tot_compl,
avg_assign_tm, median_assign_tm, start_tm, end_tm
FROM TABLE (get_daily_user_totals(:aUser));
Some notes on the above:
- If it is necessary to write into any table (as in the example above), then we must declare our function to work as an AUTONOMOUS TRANSACTION (see PRAGMA AUTONOMOUS_TRANSACTION).
- The TABLE resulting from the select statement above can be joined with other tables and, possibly, create a view.
- There have been cases where I have used a view (to make life of Java developers easier). In this case, if our PIPELINED function requires a parameter, then this can be passed via a context (using SYS_CONTEXT). I do not have an example ready for this, but I remember an article in Oracle Magazine describing this technique.
No comments:
Post a Comment