Lecture - 09 PLSQL Packages & Oracle Pipelined Functions
Lecture - 09 PLSQL Packages & Oracle Pipelined Functions
Instructor:
▪ Eric Maniraguha | eric.maniraguha@auca.ac.rw | LinkedIn Profile
6h00 pm – 8h50 pm
▪ Monday A -G209
▪ Tuesday B-G209
▪ Wednesday E-G209 April 2025
▪ Thursday F-G308 1
Database development with PL/SQL
Reference reading
▪ What is Package in Oracle?
▪ Pipelined Table Functions
▪ Pipelined Table Functions
▪ What is PL/SQL? Explained
▪ SQL CASE Statement with Multiple Conditions
▪ Database Security
▪ 7 Coding PL/SQL Procedures and Packages
In modern data systems, both PL/SQL Packages and Data Pipelines play a vital role in handling and processing data effectively—but at different
layers of the system.
▪ A PL/SQL Package is used inside the database to organize and encapsulate business logic, such as procedures, functions, and variables. It
improves modularity, performance, and reusability of database code.
▪ A Data Pipeline, on the other hand, is used to move and transform data across systems—from raw data sources to final destinations like data
warehouses or dashboards. It ensures automation, consistency, and scalability in data flow.
3
PL/SQL Package
A PL/SQL Package is a schema object in Oracle that groups related procedures, functions, variables, constants, cursors, and exceptions. It
promotes modular programming and is stored in the database.
4
PL/SQL Package Structure Diagram
Specification
Application Body
Package Database
Local Variable =
visible to the package
5
Advantages of PL/SQL Packages
1. Modular Code
3. Improved Performance
▪Oracle loads the entire package into memory on the first reference.
▪Reduces disk I/O and improves execution speed for subsequent calls.
7
Example I : Greeting Package
9
Example II : Greeting Package
10
PL/SQL Packages Without Specification - Summary
Use Cases:
▪ Internal utilities or logging.
▪ Placeholder for future public spec.
▪ Logic only triggered by internal DB processes.
11
Scenario Question: Creating packages and procedures.
Create a PL/SQL package and procedure to manage and display transaction data using an associative array.
Requirements:
1. Package trans_data:
▪ Define record types for TimeRec (minutes, hours) and TransRec (category, account, amount, time_of).
▪ Declare a constant minimum_balance (10.00) and an integer number_processed.
▪ Define an exception insufficient_funds.
2. Package aa_pkg:
▪ Define an associative array type aa_type indexed by VARCHAR2(15) and containing integers.
3. Procedure print_aa:
▪ Accept an associative array aa and print each element and its key.
4. Test Block:
▪ Initialize an associative array with keys 'zero', 'one', and 'two' and values 0, 1, and 2. Call print_aa to display the results.
Expected Output:
This question tests understanding of:
0 zero ▪ Creating packages and procedures.
1 one ▪ Working with records and associative arrays (index-by tables).
2 two ▪ Using FIRST, NEXT, and DBMS_OUTPUT to print data in PL/SQL.
Would you like to adjust the question or add more details?
12
Solution: Creating packages and procedures.
1. Package trans_data: Defines a package to manage transaction data. 2. Package aa_pkg: Defines a collection type (associative array) to store integers
CREATE OR REPLACE PACKAGE trans_data AS indexed by VARCHAR2.
-- Define a record type to hold time information (minutes and hours)
TYPE TimeRec IS RECORD ( CREATE OR REPLACE PACKAGE aa_pkg IS
minutes SMALLINT, -- Define an associative array type (indexed by VARCHAR2(15) and containing integers)
hours SMALLINT); TYPE aa_type IS TABLE OF INTEGER INDEX BY VARCHAR2(15);
END;
-- Define a record type for transaction data, including a nested TimeRec /
TYPE TransRec IS RECORD (
category VARCHAR2(10),
account INT,
amount REAL, 3. Procedure print_aa: This procedure accepts an associative array aa of
time_of TimeRec); type aa_pkg.aa_type and prints each element and its corresponding key.
-- Constant for the minimum balance required in the transaction CREATE OR REPLACE PROCEDURE print_aa (
minimum_balance CONSTANT REAL := 10.00; aa aa_pkg.aa_type -- Accepts the array as a parameter
) IS
-- Variable to keep track of the number of transactions processed i VARCHAR2(15); -- Variable to hold the current index
number_processed INT; BEGIN
-- Start with the first element
-- Exception to handle insufficient funds i := aa.FIRST;
insufficient_funds EXCEPTION;
END trans_data;
-- Loop through the array until no more elements are left
/
WHILE i IS NOT NULL LOOP
-- Print the value and the index of the array element
DBMS_OUTPUT.PUT_LINE (aa(i) || ' ' || i);
-- Move to the next index in the array
i := aa.NEXT(i);
END LOOP;
END;
13
/
Solution: Creating packages and procedures.
DECLARE
-- Declare an associative array variable of type aa_pkg.aa_type
aa_var aa_pkg.aa_type;
BEGIN
-- Assign values to the array with string keys
aa_var('zero') := 0; 4. Test Block: This anonymous block initializes an associative array aa_var, assigns values to it,
aa_var('one') := 1; and then calls the print_aa procedure to display the values.
aa_var('two') := 2;
Output
14
Question:
You are tasked with creating a PL/SQL package that handles employee bonuses and tax calculations, along with error logging
functionality. Follow the steps below:
Scenario Question: ▪ Declare a procedure calculate_bonus that calculates the bonus as 10% of an employee’s salary, ensuring the
bonus does not exceed max_bonus.
Creating a PL/SQL
▪ Declare a function calculate_tax that calculates a 15% tax on a given purchase amount.
3. Package Body:
▪ Implement the calculate_bonus procedure as described in the specification.
Package for Bonus ▪ Implement the calculate_tax function as described in the specification.
▪ Add a private procedure log_error in the package body to handle logging of error messages (leave the logic
15
PROCEDURE log_error(p_message IN VARCHAR2) IS
BEGIN
INSERT INTO error_log_table (log_time, message)
VALUES (SYSDATE, p_message);
EXCEPTION
WHEN OTHERS THEN
NULL; -- Prevents the logging procedure from raising exceptions
END log_error;
6.Test the error logging by calling the log_error procedure with a sample message such as Test log entry. After executing
the anonymous block, check the error_log_table to confirm that the message was successfully inserted into the table.
Creating a PL/SQL
o Message: Test log entry
o Log Time: The timestamp of when the log was created.
16
Solution: Creating packages and procedures.
Step 3: Create the Package Body
Step 1: Create the Error Logging Table
-- Package Body
CREATE TABLE error_log_table ( CREATE OR REPLACE PACKAGE BODY employee_package IS
log_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
log_time DATE DEFAULT SYSDATE, -- Implementation of the procedure to calculate bonus
message VARCHAR2(4000) PROCEDURE calculate_bonus(p_salary IN NUMBER, p_bonus OUT NUMBER) IS
); BEGIN
p_bonus := p_salary * 0.10; -- Calculate 10% of the salary as bonus
IF p_bonus > max_bonus THEN
p_bonus := max_bonus; -- Cap the bonus at max_bonus
Step 2: Create the PL/SQL Package Specification END IF;
END calculate_bonus;
-- Package Specification
CREATE OR REPLACE PACKAGE employee_package IS -- Implementation of the function to calculate tax
-- Declare a constant for the maximum bonus FUNCTION calculate_tax(p_purchase_amount IN NUMBER) RETURN NUMBER IS
max_bonus CONSTANT NUMBER := 5000; v_tax NUMBER;
BEGIN
-- Declare a procedure to calculate bonus v_tax := p_purchase_amount * 0.15; -- Calculate 15% tax on the purchase
PROCEDURE calculate_bonus(p_salary IN NUMBER, p_bonus OUT NUMBER); amount
RETURN v_tax;
-- Declare a function to calculate tax END calculate_tax;
FUNCTION calculate_tax(p_purchase_amount IN NUMBER) RETURN NUMBER;
-- Private procedure to log errors
PROCEDURE log_error(p_message IN VARCHAR2) IS
-- Declare a procedure for error logging (private)
BEGIN
PROCEDURE log_error(p_message IN VARCHAR2); INSERT INTO error_log_table (log_time, message)
END employee_package; VALUES (SYSDATE, p_message);
/ EXCEPTION
WHEN OTHERS THEN
NULL; -- Avoid infinite loops if logging fails
END log_error;
END employee_package;
/ 17
Solution & Testing: Creating packages and procedures.
Output
Step 4: Test the Package with an Anonymous Block
SET SERVEROUTPUT ON;
DECLARE
v_bonus NUMBER;
v_tax NUMBER;
BEGIN
-- Test the calculate_bonus procedure
employee_package.calculate_bonus(5000, v_bonus);
DBMS_OUTPUT.PUT_LINE('Calculated Bonus: ' || v_bonus);
18
PL/SQL Package public (global) and local (private) variables
In PL/SQL, a package can contain both public (global) and local (private) variables. Public variables are accessible to any code that can access the package,
whereas local variables are only accessible within the package body.
CREATE OR REPLACE PACKAGE employee_package IS
max_bonus CONSTANT NUMBER := 1000; -- public variable
Key Differences:
▪ Public variables (like max_bonus) can be accessed outside the package by referencing the package name, whereas local variables (like v_temp_bonus) are only visible
within the package body.
▪ Public variables are declared in the package specification (IS section), while local variables are declared in the package body (BODY section).
19
-- Package Specification Package Specification
CREATE OR REPLACE PACKAGE employee_pkg AS
-- Public variable (accessible outside the package)
emp_count NUMBER := 0;
-- Public procedure
PROCEDURE add_employee;
END employee_pkg;
/
Body Package
-- Package Body
CREATE OR REPLACE PACKAGE BODY employee_pkg AS
-- Implementation of the public procedure
PROCEDURE add_employee IS
BEGIN
emp_count := emp_count + 1;
20
What is Package Encapsulation in PL/SQL?
Encapsulation refers to hiding the internal implementation details of a package and exposing only what’s necessary to the outside world. It is one of the
key principles of modular programming and helps in:
▪ Reducing complexity
▪ Improving maintainability
▪ Securing internal logic
Structure of a Package
A PL/SQL package has two parts:
1. Package Specification:
1. Declares public elements (procedures, functions, variables, constants).
2. Acts as an interface to users.
2. Package Body:
1. Implements the logic of the procedures and functions.
2. May include private elements (only accessible within the package body).
21
PL/SQL Encapsulation example
-- Public procedure
PROCEDURE deposit(p_amount NUMBER) IS
BEGIN Summary:
balance := balance + p_amount; Encapsulation via packages allows you to:
log_transaction('Deposit', p_amount); -- internal call ▪ Expose only what's needed (via the package spec).
END; ▪ Keep internals private (in the package body).
END bank_pkg; ▪ Build secure and maintainable PL/SQL applications.
/
22
Scenario Question: Basic Employee Management System –
Encapsulation
A small company wants to automate how it stores and retrieves employee information. The HR team needs a system that allows them to:
1.Add new employees with personal and job-related details like name, contact info, hire date, department, role, and salary.
2.Find the name of a department an employee belongs to by using the employee's ID.
To make the system organized and reusable, the company wants all related functionality to be grouped in a PL/SQL package named emp_mgmt.
Student Task
Create a PL/SQL package emp_mgmt with:
▪ A procedure add_employee that inserts a new employee into an employees table.
▪ A function get_department that returns the department name for a given employee ID using the departments table.
Ensure your solution follows good practices like encapsulation and handles missing data gracefully.
23
CREATE OR REPLACE PACKAGE BODY emp_mgmt AS
Body Package
-- Implementation of the procedure to add a new employee
PROCEDURE add_employee(
p_first_name VARCHAR2,
p_last_name VARCHAR2,
p_email VARCHAR2,
p_phone_number VARCHAR2,
p_address VARCHAR2,
p_hire_date DATE,
p_department_id NUMBER,
p_role_id NUMBER,
p_salary NUMBER
) IS
BEGIN
-- Insert the new employee record into the EMPLOYEES table Using the package
INSERT INTO employees (
/* Using the Package */
first_name, last_name, email, phone_number, address, hire_date,
BEGIN
department_id, role_id, salary
-- Adding a new employee named John Doe with specific details
Package
);
p_email => 'john.doe@example.com',
END add_employee;
p_phone_number => '123-456-7890',
- Implementation of the function to retrieve the department name p_address => '123 Elm Street, Cityville',
name
v_department_name VARCHAR2(100); -- Variable to hold the department
p_hire_date => TO_DATE('2023-06-15', 'YYYY-MM-DD'),
p_department_id => 3, -- Assuming 3 is the department ID
for IT
BEGIN
p_role_id => 5, -- Assuming 5 is the role ID for
-- Retrieve the department name based on the employee's departt ID Software Engineer
SELECT department_name
p_salary => 65000 -- Salary amount in the local currency
INTO v_department_name
);
FROM departments
END;
WHERE department_id = (
/
SELECT department_id
FROM employees
WHERE employee_id = p_emp_id
);
RETURN v_department_name; -- Return the retrieved department name
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Handle case where no department is found for the given employee
ID
RETURN NULL;
END get_department;
END emp_mgmt;
/ 24
Solution: Basic EMS – Package Encapsulation & Result
Testing
/*
Retrieving the Department Name for an Employee
To get the department name for a specific employee based on their employee ID,
you can call the get_department function. Below is an example of how to use the function.
*/
DECLARE
v_dept_name VARCHAR2(100); -- Variable to hold the department name
BEGIN
-- Get the department name for employee with ID 1001
v_dept_name := emp_mgmt.get_department(p_emp_id => 3);
Testing
/*
Testing the Code
*/
SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID = 3; 25
Explore Advanced Concepts in Package Encapsulation
For better exploration and enhancement of package encapsulation, I highly recommend referring to the advanced exercise available in my GitHub repository.
Security Risk / Consideration Potential privilege escalation if not properly controlled All users must have direct privileges on accessed objects
PL/SQL Package
BEGIN
-- Code runs with privileges of the package owner
NULL;
AUTHID Clause
/
Flow:
1. User A has been granted SELECT on C.employee_data.
2. User A creates procedure read_data using this table.
3. User B executes the procedure.
Result: Success - because the code runs with User A’s privileges, even though User B doesn’t have access to C.employee_data.
Note: This is useful for hiding data or providing controlled access via APIs.
29
Scenario : Managing User Privileges in Oracle PL/SQL –
Summary Table
30
Scenario: Centralized Reporting Package in a Bank
Players:
▪ DBA team / IT Department (User: central_admin) — owns a package called report_pkg.
▪ Bank branches (Users: branch_kigali, branch_musanze, etc.) — each has a schema with local data, including a table transactions.
Problem:
The central IT department wants to write one package to generate reports for any branch's transaction data. But each branch stores their own transactions table in their
own schema.
If the package is defined with definer's rights (default), it will always look in central_admin.transactions — which doesn't make sense.
31
Scenario: Centralized Reporting Package in a Bank - uses AUTHID
DEFINER (default behavior) |uses AUTHID CURRENT_USER (invoker’s
rights).
The central admin user (central_admin) is responsible for maintaining shared PL/SQL 2. report_invoker_pkg — Invoker's Rights (AUTHID CURRENT_USER)
packages. -- Created by central_admin
CREATE OR REPLACE PACKAGE report_invoker_pkg AUTHID CURRENT_USER IS
--Each bank branch (branch_kigali, branch_musanze, etc.) has its own transactions table with
PROCEDURE summary_report;
this structure:
END;
/
CREATE TABLE transactions (
trans_id NUMBER,
CREATE OR REPLACE PACKAGE BODY report_invoker_pkg IS
amount NUMBER,
PROCEDURE summary_report IS
trans_date DATE
v_total NUMBER;
);
BEGIN
SELECT SUM(amount) INTO v_total FROM transactions
1. report_definer_pkg — Definer's Rights (AUTHID DEFINER) WHERE trans_date = TRUNC(SYSDATE);
-- Created by central_admin
CREATE OR REPLACE PACKAGE report_definer_pkg IS DBMS_OUTPUT.PUT_LINE('Invoker\'s Rights Report - Total: ' || v_total);
PROCEDURE summary_report; END;
END; END;
/ /
This package accesses transactions in the caller’s schema — e.g.,
CREATE OR REPLACE PACKAGE BODY report_definer_pkg IS branch_kigali.transactions if the caller is branch_kigali.
PROCEDURE summary_report IS
v_total NUMBER;
BEGIN
SELECT SUM(amount) INTO v_total FROM transactions
WHERE trans_date = TRUNC(SYSDATE);
EXEC report_definer_pkg.summary_report;
EXEC report_invoker_pkg.summary_report;
in a Bank – Test
▪ report_invoker_pkg.summary_report
➜ Works and fetches data from branch_kigali.transactions
➜ Accesses caller's schema (AUTHID CURRENT_USER)
Behavior
Summary Table:
33
Scenario Question: Implementing Role-Based Access
Control in a Banking Application using AUTHID
You've presented an interesting banking scenario that leverages PL/SQL's AUTHID clause to implement role-based access control. Let me formulate this as a question and provide a
detailed answer.
Question
How can we use PL/SQL's AUTHID clause to implement role-based security in a banking application where different roles (Tellers, Managers, and Auditors) need different levels of access
to customer data and transactions?
Answer
Understanding Role-Based Requirements
In a banking application, we need to enforce different privileges for:
▪ Tellers: Create regular transactions
▪ Managers: Approve high-value transactions
▪ Auditors: View but not modify any customer data
34
Scenario Question: Healthcare System with Multi-Level
Access
In a healthcare system where users have different roles—such as Doctors, Nurses, and Administrators—how can PL/SQL’s AUTHID clause be used to enforce role-based access control
to ensure that each user only accesses or modifies patient data according to their privileges?
Scenario Description
In a healthcare information system, various users interact with patient data, but their access levels differ:
▪ Doctors: Can view and update sensitive diagnosis and treatment data.
▪ Nurses: Can update only basic patient information, such as vitals or contact details.
▪ Administrators: Can view all patient data, including medical records, but cannot modify any diagnosis or treatment information.
35
Scenario Question: Multi-Tenant SaaS Application
In a Software-as-a-Service (SaaS) application that serves multiple organizations (tenants), each organization’s data must be isolated and accessible only by users from that
organization.
▪ Problem: Procedures that access data should be restricted to data that belongs only to the user’s organization. For example, get_employee_records should only return
employees from the user’s organization, not other organizations.
By combining AUTHID DEFINER and AUTHID CURRENT_USER, the SaaS application can isolate tenant data while still allowing flexible access control.
In each of these scenarios, the AUTHID clause allows developers to control how privileges are applied to
stored procedures and packages, ensuring secure and context-specific access in multi-user and multi-role
environments.
36
Implementing Secure Role-Based Access in Oracle PL/SQL
Using the AUTHID Clause
▪ AUTHID DEFINER allows a PL/SQL package or procedure to execute with the privileges of the user who -- Create a package with AUTHID DEFINER
defined it, regardless of who is calling it. This can enhance security and simplify permission management CREATE OR REPLACE PACKAGE my_package AUTHID DEFINER IS
in multi-user databases. PROCEDURE insert_value(p_id NUMBER, p_value VARCHAR2);
▪ Conversely, AUTHID CURRENT_USER executes with the privileges of the user calling the procedure, END my_package;
which is more flexible but may require careful management of permissions. /
Step 1: Create the Package Specification CREATE OR REPLACE PACKAGE BODY pattern_package AS
This defines the public interface - what functions and procedures are available for use. -- Procedure to draw a normal triangle pattern
PROCEDURE draw_triangle(p_rows IN NUMBER) IS
CREATE OR REPLACE PACKAGE pattern_package AS BEGIN
-- Procedure to draw a normal triangle pattern FOR i IN 1..p_rows LOOP
PROCEDURE draw_triangle(p_rows IN NUMBER); FOR j IN 1..i LOOP
DBMS_OUTPUT.PUT('*');
-- Procedure to draw an inverted triangle pattern END LOOP;
PROCEDURE draw_inverted_triangle(p_rows IN NUMBER); DBMS_OUTPUT.PUT_LINE('');
END pattern_package; END LOOP;
/ END draw_triangle;
38
Step 3: Calling the Procedures from the Package
BEGIN BEGIN
pattern_package.draw_triangle(5); -- Drawing a triangle with 5 pattern_package.draw_inverted_triangle(5); -- Drawing an
rows inverted triangle with 5 rows
END; END;
/ /
39
What is Data Pipeline?
A data pipeline is a set of processes or tools that move data from one system (like a database, file system, or API) to another, often through stages
of collection, transformation, and storage. Think of it like a factory line for data: raw data comes in, gets cleaned and reshaped, and ends up in a
form that's ready for analysis or use in applications.
1. Source Systems
▪ Examples: Salesforce, Oracle ERP Cloud, Workday
▪ These systems generate and store raw business data.
▪ In an Oracle environment, data from these sources can be
accessed via:
o External Tables
o SQL*Loader
o Connectors or APIs
Extract:
▪ Data is read from the source systems.
▪ External tables or connectors fetch the raw data.
Transform:
▪ Transformation logic (e.g., filtering, cleansing, calculations) is applied on-the-fly using pipelined table functions.
▪ No need to write data to intermediate staging tables.
Load:
▪ Transformed data is immediately inserted into the target data warehouse.
▪ This reduces latency and improves processing speed.
3. Data Warehouse
▪ Acts as the centralized storage for transformed, ready-to-use data.
▪ Since pipelined functions stream the data directly, loading is faster and more scalable—ideal for real-time or high-volume data.
42
Why Use a Pipelined Function?
Improves
performance by
streaming rows instead
of materializing them.
Why Use a
Pipelined
Function?
Useful for transforming
Integrates well with
data row-by-row in real-
SQL queries.
time.
43
where NOT to use Oracle pipelined functions in PL/SQL
In DML operations (INSERT, UPDATE) Pipelined functions are read-only, not meant to modify data. Use standard PL/SQL procedures or triggers.
Inside scalar context They return collections, not single values. Use scalar functions instead.
For small, simple datasets Adds unnecessary complexity and overhead. Use regular table functions or direct SQL.
Without parallel enable settings Pipelined functions won't parallelize unless properly configured. Use native parallel SQL or properly configured pipelines.
In function-based indexes Not supported due to non-determinism. Use simple scalar functions instead.
44
Regular vs Pipelined Table Function in Oracle PL/SQL
Definition Returns a collection as a whole. Returns rows one at a time (streaming fashion).
Return Type Collection (e.g., TABLE OF RECORD or TABLE OF OBJECT). Same – but uses PIPELINED keyword.
Execution Timing Waits until function finishes processing all data before returning. Streams data as it's processed — faster first row response.
Performance on Large Data Slower for large data (entire dataset must be built in memory). Better — returns data incrementally, reducing memory use.
Complex Logic Handling Easier to handle complex transformations at once. Best for row-by-row lightweight logic.
Memory Usage High — entire result set stored before returning. Low — row-by-row output, no full dataset needed in memory.
Use Case Small datasets, batch processing. Streaming large datasets, ETL, integration with external sources.
Syntax Example RETURN my_type; RETURN my_type PIPELINED; and use PIPE ROW (record);
45
Key Stages & Features of Good Data Pipeline
Feature Description
Automation Can run without manual intervention using schedulers or event triggers.
Logging & Auditing Keeps track of what was processed and when (useful for debugging).
Oracle Pipelined
CREATE OR REPLACE TYPE collection_type_name AS TABLE OF object_type_name;
/
Table Function –
Syntax Overview Step 3: Create the Pipelined Table Function
Use cases:
A branch wants to generate daily transactions as a table that can be used in SELECT statements, BI tools, joins, or views.
-- This returns the daily transactions as if you're querying a table. Very useful in reporting
or joining with other tables.
49
Oracle Pipelined Table Functions
A pipelined table function returns a collection (like a table) that can be queried in SQL as if it were a table. Unlike regular functions, pipelined functions allow results to be "streamed"
back row by row using the PIPE ROW syntax.
Create an Object Type
Key Components -- Step 1: Create an object type
1. Object Type: Defines the structure of a row. CREATE OR REPLACE TYPE emp_obj AS OBJECT (
2. Table Type: A collection type based on the object type. emp_id NUMBER,
3. Pipelined Function: Uses PIPELINED keyword and PIPE ROW statements. emp_name VARCHAR2(100)
);
Summary
Use Them When... Avoid If...
You need to stream large results You’re working with small/simple datasets
You want custom row-by-row logic Simpler logic can be done in pure SQL
SQL-only solutions are too limiting You want minimal overhead and maintenance 51
Scenario: Benefits of Oracle Pipelined Table Functions in ETL
Scenario
You want to retrieve allowances for a given employee using a pipelined table function. The function returns a table with the following fields:
▪ ALLOWANCE_ID
▪ ALLOWANCE_AMOUNT
▪ EFFECTIVE_DATE
Step 3: Create the Pipelined Table Function
This function returns a table of allowances for a given employee using a pipeline approach.
Step 1: Define an Object Type
-- Create a pipelined function to retrieve employee allowances
Define a custom object to represent the structure of an individual allowance record.
CREATE OR REPLACE FUNCTION get_employee_allowances(emp_id IN NUMBER)
-- Create an object type to represent one allowance record RETURN allowance_table PIPELINED
CREATE OR REPLACE TYPE allowance_record AS OBJECT ( AS
ALLOWANCE_ID NUMBER, BEGIN
ALLOWANCE_AMOUNT NUMBER, -- Loop through each matching record from the employee_allowances table
EFFECTIVE_DATE DATE FOR rec IN (
); SELECT allowance_id, allowance_amount, effective_date
/ FROM employee_allowances
WHERE employee_id = emp_id
)
LOOP
-- Pipe each record into the output stream
PIPE ROW(allowance_record(rec.allowance_id, rec.allowance_amount,
rec.effective_date));
END LOOP;
Step 1: Define an Object Type Step 3: Create the Pipelined Table Function
-- Create a pipelined function to retrieve employee allowances
-- Create an object type to represent one allowance record CREATE OR REPLACE FUNCTION get_employee_allowances (
CREATE OR REPLACE TYPE allowance_record AS OBJECT ( p_employee_id NUMBER
ALLOWANCE_ID NUMBER, ) RETURN allowance_table PIPELINED IS
ALLOWANCE_AMOUNT NUMBER,
EFFECTIVE_DATE DATE BEGIN
); -- Loop through all applicable allowances for the given employee
/ FOR r IN (
SELECT a.ALLOWANCE_ID, a.ALLOWANCE_AMOUNT, a.EFFECTIVE_DATE
Step 2: Define a Table Type FROM ALLOWANCES a
JOIN EMPLOYEES e ON a.ROLE_ID = e.ROLE_ID
-- Create a table type (collection) of allowance_record objects WHERE e.EMPLOYEE_ID = p_employee_id
CREATE OR REPLACE TYPE allowance_table AS TABLE OF allowance_record; AND a.IS_APPLICABLE = 'Y’
/ AND a.EFFECTIVE_DATE <= SYSDATE
) LOOP
-- Pipe each selected row into the result set
PIPE ROW(allowance_record(r.ALLOWANCE_ID, r.ALLOWANCE_AMOUNT,
r.EFFECTIVE_DATE));
Output END LOOP;
SELECT *
FROM TABLE(get_employee_allowances(101)); -- Replace 101 with any valid
54
EMPLOYEE_ID
Question:
Task:
Write an anonymous PL/SQL block that:
1. Accepts an EMPLOYEE_ID as input (choose a valid one from your data),
2. Calls the procedure adjust_employee_salary from the employee_management_pkg package,
3. Displays the original and updated salary using DBMS_OUTPUT.PUT_LINE.
Employee Salaries
Modify your PL/SQL block to:
▪ Display a custom message when the salary was adjusted based on bonus vs. fixed increment.
▪ Include exception handling in your test block.
Using a PL/SQL
Package Procedure
55
-- Create the package specification Package Specification
CREATE OR REPLACE PACKAGE employee_management_pkg AS
FUNCTION calculate_bonus(salary IN NUMBER) RETURN NUMBER; -- Function parameter
PROCEDURE adjust_employee_salary(emp_id IN NUMBER, salary IN OUT NUMBER, new_salary OUT NUMBER); -- Procedure parameters
END employee_management_pkg;
/
Solution: Adjusting
-- Check if the employee exists and fetch the current salary
SELECT SALARY INTO salary
FROM EMPLOYEES
Employee Salaries
WHERE EMPLOYEE_ID = emp_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee with ID ' || emp_id || ' not found.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An error occurred: ' || SQLERRM);
END adjust_employee_salary;
END employee_management_pkg;
/
56
SET SERVEROUTPUT ON; Test our Function inside the package Output
DECLARE
v_salary NUMBER := 40000;
v_bonus NUMBER;
BEGIN
-- Call the function from the package
v_bonus := employee_management_pkg.calculate_bonus(v_salary);
DBMS_OUTPUT.PUT_LINE('Bonus for salary ' || v_salary || ' is: ' || v_bonus);
END;
/
Test: Adjusting
Employee Salaries Test our Procedure inside the package
Scenario-Based Question:
You are working as a PL/SQL developer for an HR management system in a large company. The Human Resources department often needs to review salary adjustments and bonus eligibility for
employees without making direct changes to the database. To improve performance and enable flexible reporting, the team has requested a solution that can stream data in a queryable format.
EMPLOYEE_ID NUMBER
FIRST_NAME VARCHAR2(50)
LAST_NAME VARCHAR2(50)
ROLE_ID NUMBER
SALARY NUMBER
58
Creating a Pipelined Table Function for Real-Time Salary
Adjustment Insights in HR Systems (2/2)
Task Instructions:
1. Create an object type (e.g., salary_bonus_rec) to represent a record with the required fields:
EMPLOYEE_ID, SALARY, BONUS, and NEW_SALARY.
2. Create a collection type (e.g., salary_bonus_tab) as a table of the object type.
3. Create a helper function (optional) calculate_bonus(salary NUMBER) RETURN NUMBER that applies the bonus logic.
4. Create the pipelined function (e.g., get_adjusted_salaries) that:
▪ Returns salary_bonus_tab PIPELINED
▪ Pipes each employee's computed values
5. Finally, test the function by querying it using:
59
Question: Oracle Pipeline
Question:
Create a pipelined function that returns information about products and their pricing. Your task is to write a function named get_product_pricing that accepts a product
category ID as input and returns a pipelined table containing the product ID, product name, and product price for each product in that category.
Steps:
1. Create a table named products with the following columns:
▪ product_id (NUMBER): Primary key, representing the product ID.
▪ product_name (VARCHAR2(100)): Name of the product.
▪ category_id (NUMBER): Represents the category ID of the product.
▪ price (NUMBER): Price of the product.
2. Insert sample data into the products table for at least three categories, each with several products.
3. Define an object type named product_pricing_record with the following attributes:
▪ product_id (NUMBER)
▪ product_name (VARCHAR2(100))
▪ price (NUMBER)
4. Create a table type named product_pricing_table based on product_pricing_record to store multiple rows of product_pricing_record.
5. Write the get_product_pricing function to:
▪ Accept a category_id as an input parameter.
▪ Return a pipelined table of type product_pricing_table.
▪ Loop through each product in the specified category and return each row in the pipeline with the product's ID, name, and price.
60
61
Conclusion
Key Takeaways
Oracle Packages:
▪ Modularity: Packages group related procedures, functions, variables, and cursors into a single unit for better organization.
▪ Encapsulation: Public specifications expose only what's necessary, hiding implementation details.
▪ Reusability & Maintainability: Once compiled, packages can be reused across different PL/SQL programs, simplifying maintenance.
▪ Performance: Packages load into memory once and allow faster access to bundled objects.
Conclusion: Combining Oracle Packages and Pipelined Table Functions is a powerful way to build performant, modular, and scalable PL/SQL-based data transformation pipelines
— essential for real-time analytics and ETL workflows.
62
Keep Learning!
63