PL/SQL
Updated: May 22, 2026Categories: Query
Printed from:
Oracle PL/SQL Comprehensive Cheatsheet
1. Basic PL/SQL Structure and Syntax
Block Structure
SQL
123456789DECLARE
-- Variable declarations
BEGIN
-- Executable statements
EXCEPTION
-- Exception handling
END;
/
Basic Syntax Rules
- Statements end with semicolon
; - Block ends with
END; - Anonymous block executed with
/ - Case-insensitive language
- Comments:
- Single line:
-- - Multi-line:
/* comment */
- Single line:
- Identifiers up to 128 bytes (since 12.2)
2. Data Types and Variable Declarations
Scalar Data Types
SQL
123456789101112131415161718192021222324252627-- Numeric Types
v_integer INTEGER;
v_number NUMBER(10,2); -- Precision, Scale
v_binary_float BINARY_FLOAT;
v_binary_double BINARY_DOUBLE;
v_pls_integer PLS_INTEGER; -- Faster than NUMBER for counters
v_simple_int SIMPLE_INTEGER; -- Non-null, no overflow checking (fastest)
-- Character Types
v_char CHAR(10); -- Fixed-length
v_varchar VARCHAR2(32767); -- Up to 32767 bytes in PL/SQL
v_clob CLOB; -- Preferred over deprecated LONG
v_nvarchar NVARCHAR2(100); -- Unicode
-- Date and Time Types
v_date DATE;
v_timestamp TIMESTAMP;
v_timestamp_tz TIMESTAMP WITH TIME ZONE;
v_interval_ym INTERVAL YEAR TO MONTH;
v_interval_ds INTERVAL DAY TO SECOND;
-- JSON Type (since 21c, native binary JSON)
v_json JSON;
-- Boolean (PL/SQL only; supported as a SQL type since 23ai)
v_flag BOOLEAN;
Note:
LONGandLONG RAWare deprecated — useCLOB/BLOBfor new code.
Variable Declaration Techniques
SQL
123456789101112131415-- Basic declaration
salary NUMBER(8,2);
-- With initialization
total_sales NUMBER(10,2) := 0;
-- Declaring using %TYPE (reference column type)
emp_salary employees.salary%TYPE;
-- Default values
counter INTEGER DEFAULT 0;
-- NOT NULL constraint requires initialization
v_id NUMBER NOT NULL := 0;
3. Constants and Literals
SQL
1234567891011-- Declaring constants
PI CONSTANT NUMBER := 3.14159;
MAX_EMPLOYEES CONSTANT INTEGER := 1000;
-- Literal examples
string_literal VARCHAR2(50) := 'Hello, World!';
q_string VARCHAR2(50) := q'[O'Brien's data]'; -- Q-quoting
numeric_literal NUMBER := 42;
date_literal DATE := DATE '2026-01-01';
ts_literal TIMESTAMP := TIMESTAMP '2026-01-01 12:00:00';
4. Control Structures
Conditional Statements
SQL
1234567891011121314151617181920212223-- IF-THEN-ELSE
IF salary > 50000 THEN
bonus := salary * 0.1;
ELSIF salary > 30000 THEN
bonus := salary * 0.05;
ELSE
bonus := 0;
END IF;
-- CASE Statement
CASE department
WHEN 'Sales' THEN commission_rate := 0.15;
WHEN 'Marketing' THEN commission_rate := 0.10;
ELSE commission_rate := 0.05;
END CASE;
-- Searched CASE expression
v_grade := CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
ELSE 'C'
END;
Loops
SQL
1234567891011121314151617181920212223242526272829303132-- Basic LOOP
LOOP
-- statements
EXIT WHEN condition;
END LOOP;
-- Numeric FOR loop
FOR i IN 1..10 LOOP
-- statements
END LOOP;
-- FOR loop with step (since 21c)
FOR i IN 1..100 BY 2 LOOP
-- iterates 1, 3, 5, ...
END LOOP;
-- FOR loop iterating over a collection (since 21c)
FOR v_item IN VALUES OF my_collection LOOP
-- ...
END LOOP;
-- WHILE Loop
WHILE counter <= 100 LOOP
counter := counter + 1;
END LOOP;
-- CONTINUE / CONTINUE WHEN
FOR i IN 1..10 LOOP
CONTINUE WHEN MOD(i,2) = 0;
-- runs only for odd i
END LOOP;
5. Cursors
Implicit Cursor
SQL
12345678-- Implicit cursor used in SELECT INTO
SELECT salary INTO emp_salary
FROM employees
WHERE employee_id = 100;
-- Implicit cursor attributes
-- SQL%FOUND, SQL%NOTFOUND, SQL%ROWCOUNT, SQL%ISOPEN
Explicit Cursor
SQL
1234567891011121314151617181920212223242526272829-- Cursor Declaration
CURSOR emp_cursor IS
SELECT employee_id, salary FROM employees;
-- Cursor with Parameters
CURSOR dept_cursor(p_dept_id NUMBER) IS
SELECT * FROM employees WHERE department_id = p_dept_id;
-- Cursor FOR Loop (recommended; implicit OPEN/FETCH/CLOSE)
BEGIN
FOR emp_rec IN (SELECT * FROM employees) LOOP
DBMS_OUTPUT.PUT_LINE(emp_rec.last_name);
END LOOP;
END;
-- Manual cursor processing
DECLARE
CURSOR c_employees IS SELECT * FROM employees;
emp_record c_employees%ROWTYPE;
BEGIN
OPEN c_employees;
LOOP
FETCH c_employees INTO emp_record;
EXIT WHEN c_employees%NOTFOUND;
-- Process record
END LOOP;
CLOSE c_employees;
END;
REF CURSOR
SQL
1234567891011121314DECLARE
TYPE emp_refcur IS REF CURSOR;
rc emp_refcur;
r employees%ROWTYPE;
BEGIN
OPEN rc FOR 'SELECT * FROM employees WHERE department_id = :d' USING 10;
LOOP
FETCH rc INTO r;
EXIT WHEN rc%NOTFOUND;
-- process
END LOOP;
CLOSE rc;
END;
6. Exception Handling
Predefined Exceptions
SQL
123456789101112131415BEGIN
SELECT salary INTO v_salary
FROM employees
WHERE employee_id = -1; -- Will raise NO_DATA_FOUND
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No employee found');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Multiple employees found');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Unexpected error: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
RAISE; -- best practice: re-raise unless truly handled
END;
User-Defined Exceptions
SQL
1234567891011121314DECLARE
insufficient_funds EXCEPTION;
PRAGMA EXCEPTION_INIT(insufficient_funds, -20010);
account_balance NUMBER;
BEGIN
IF account_balance < withdrawal_amount THEN
RAISE insufficient_funds;
-- or: RAISE_APPLICATION_ERROR(-20010, 'Insufficient funds');
END IF;
EXCEPTION
WHEN insufficient_funds THEN
DBMS_OUTPUT.PUT_LINE('Insufficient funds');
END;
7. Stored Procedures and Functions
Stored Procedure
SQL
123456789101112131415161718192021CREATE OR REPLACE PROCEDURE calculate_bonus(
p_employee_id IN NUMBER,
p_bonus_percentage IN NUMBER
) IS
v_salary employees.salary%TYPE;
BEGIN
SELECT salary INTO v_salary
FROM employees
WHERE employee_id = p_employee_id;
UPDATE employees
SET salary = salary * (1 + p_bonus_percentage/100)
WHERE employee_id = p_employee_id;
END;
/
-- Calling the procedure
BEGIN
calculate_bonus(p_employee_id => 100, p_bonus_percentage => 10);
END;
Function
SQL
12345678910111213141516171819CREATE OR REPLACE FUNCTION get_annual_salary(
p_employee_id IN NUMBER
) RETURN NUMBER
DETERMINISTIC
RESULT_CACHE
IS
v_annual_salary NUMBER;
BEGIN
SELECT salary * 12 INTO v_annual_salary
FROM employees
WHERE employee_id = p_employee_id;
RETURN v_annual_salary;
END;
/
-- Using the function
SELECT get_annual_salary(employee_id) FROM employees;
WITH-Clause (PL/SQL in SQL)
SQL
12345678WITH
FUNCTION fmt(p NUMBER) RETURN VARCHAR2 IS
BEGIN
RETURN TO_CHAR(p, 'FM999,999.00');
END;
SELECT fmt(salary) FROM employees;
/
8. Packages
Package Specification
SQL
123456789101112131415161718CREATE OR REPLACE PACKAGE employee_pkg
AUTHID DEFINER -- or CURRENT_USER for invoker rights
IS
-- Public declarations
PROCEDURE hire_employee(
p_name IN VARCHAR2,
p_department IN VARCHAR2
);
FUNCTION get_employee_count(
p_department IN VARCHAR2
) RETURN NUMBER;
-- Public constants
max_salary CONSTANT NUMBER := 100000;
END employee_pkg;
/
Package Body
SQL
123456789101112131415161718192021222324252627CREATE OR REPLACE PACKAGE BODY employee_pkg IS
-- Private variables and constants
g_company_name VARCHAR2(100) := 'ACME Corp';
PROCEDURE hire_employee(
p_name IN VARCHAR2,
p_department IN VARCHAR2
) IS
BEGIN
-- Hiring logic
NULL;
END hire_employee;
FUNCTION get_employee_count(
p_department IN VARCHAR2
) RETURN NUMBER IS
v_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_count
FROM employees
WHERE department = p_department;
RETURN v_count;
END get_employee_count;
END employee_pkg;
/
9. Triggers
DML Trigger
SQL
1234567891011CREATE OR REPLACE TRIGGER before_insert_employee
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary < 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Salary cannot be negative');
END IF;
:NEW.hire_date := SYSDATE;
END;
/
Compound Trigger (preferred for multi-timing logic)
SQL
1234567891011121314151617181920CREATE OR REPLACE TRIGGER emp_compound_trg
FOR INSERT OR UPDATE ON employees
COMPOUND TRIGGER
TYPE id_t IS TABLE OF employees.employee_id%TYPE;
g_ids id_t := id_t();
AFTER EACH ROW IS
BEGIN
g_ids.EXTEND;
g_ids(g_ids.LAST) := :NEW.employee_id;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
-- bulk post-processing using g_ids
NULL;
END AFTER STATEMENT;
END;
/
System Trigger
SQL
1234567CREATE OR REPLACE TRIGGER logon_trigger
AFTER LOGON ON DATABASE
BEGIN
DBMS_OUTPUT.PUT_LINE('User logged in: ' || USER);
END;
/
10. Collections
Varray
SQL
123TYPE employee_list IS VARRAY(10) OF NUMBER;
v_employees employee_list := employee_list(1, 2, 3);
Nested Table
SQL
123TYPE name_list IS TABLE OF VARCHAR2(50);
v_names name_list := name_list('John', 'Jane', 'Alice');
Associative Array
SQL
123456789DECLARE
TYPE salary_map IS TABLE OF NUMBER
INDEX BY VARCHAR2(50);
employee_salaries salary_map;
BEGIN
employee_salaries('John') := 50000;
employee_salaries('Jane') := 60000;
END;
Collection Constructors and Qualified Expressions (21c+)
SQL
12345678DECLARE
TYPE int_list IS TABLE OF INTEGER;
v_nums int_list;
BEGIN
-- Qualified expression
v_nums := int_list(10, 20, 30);
END;
Common Collection Methods
COUNT, FIRST, LAST, NEXT(n), PRIOR(n), EXISTS(n), EXTEND[(n)], TRIM[(n)], DELETE[(n[,m])], LIMIT (varrays).
11. Object-Oriented Features
Object Type
SQL
12345678910111213141516171819202122CREATE OR REPLACE TYPE employee_type AS OBJECT (
employee_id NUMBER,
name VARCHAR2(100),
salary NUMBER,
MEMBER FUNCTION get_annual_salary RETURN NUMBER
) NOT FINAL;
/
CREATE OR REPLACE TYPE BODY employee_type AS
MEMBER FUNCTION get_annual_salary RETURN NUMBER IS
BEGIN
RETURN salary * 12;
END;
END;
/
-- Inheritance
CREATE OR REPLACE TYPE manager_type UNDER employee_type (
team_size NUMBER
);
/
12. Bulk Operations
BULK COLLECT
SQL
12345678910111213141516171819202122232425DECLARE
TYPE emp_id_list IS TABLE OF NUMBER;
v_emp_ids emp_id_list;
BEGIN
SELECT employee_id
BULK COLLECT INTO v_emp_ids
FROM employees
WHERE department_id = 10;
END;
-- LIMIT clause to cap memory use
DECLARE
CURSOR c IS SELECT * FROM employees;
TYPE emp_t IS TABLE OF employees%ROWTYPE;
rows emp_t;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO rows LIMIT 1000;
EXIT WHEN rows.COUNT = 0;
-- process batch
END LOOP;
CLOSE c;
END;
FORALL
SQL
123456789101112DECLARE
TYPE id_list IS TABLE OF NUMBER;
v_ids id_list := id_list(1, 2, 3);
BEGIN
FORALL i IN INDICES OF v_ids SAVE EXCEPTIONS
UPDATE employees
SET salary = salary * 1.1
WHERE employee_id = v_ids(i);
-- Inspect SQL%BULK_EXCEPTIONS / SQL%BULK_ROWCOUNT afterward
END;
13. Dynamic SQL
EXECUTE IMMEDIATE
SQL
123456789101112DECLARE
v_table_name VARCHAR2(128);
v_dynamic_sql VARCHAR2(500);
v_result NUMBER;
BEGIN
v_table_name := 'EMPLOYEES';
v_dynamic_sql := 'SELECT COUNT(*) FROM '
|| DBMS_ASSERT.SQL_OBJECT_NAME(v_table_name);
EXECUTE IMMEDIATE v_dynamic_sql INTO v_result;
END;
Security: Always use bind variables for values; use
DBMS_ASSERTto validate identifiers concatenated into SQL to prevent SQL injection.
14. Performance Optimization
- Use
BULK COLLECT ... LIMITandFORALLfor large dataset operations to reduce SQL/PL/SQL context switches. - Prefer
PLS_INTEGER/SIMPLE_INTEGERoverNUMBERfor counters. - Use
%ROWTYPEand%TYPEfor consistent type handling. - Use bind variables to avoid hard parsing.
- Annotate side-effect-free functions with
DETERMINISTICand considerRESULT_CACHE. - Use
PRAGMA UDFon standalone functions called from SQL to reduce overhead. - Mark hot procedures with
PRAGMA INLINE(name,'YES')where appropriate. - Enable native compilation:
ALTER SESSION SET PLSQL_CODE_TYPE = NATIVE; - Set
PLSQL_OPTIMIZE_LEVEL = 3(default 2) for aggressive inlining. - Use cursor
FORloops orBULK COLLECTinstead of row-by-row fetch. - Consider polymorphic table functions and SQL macros (19c+) for set-based logic in SQL rather than PL/SQL loops.
15. Debugging and Error Handling
Best Practices
- Use
DBMS_OUTPUT.PUT_LINE()for ad-hoc logging; use a logging table orUTL_FILEfor production. - Capture full context with
DBMS_UTILITY.FORMAT_ERROR_BACKTRACEandDBMS_UTILITY.FORMAT_CALL_STACK(orUTL_CALL_STACKfor structured access). - Always re-raise unexpected errors from
WHEN OTHERSunless you have a specific reason not to. - Use
RAISE_APPLICATION_ERROR(-20000..-20999, msg)for application errors. - Enable PL/SQL warnings:
ALTER SESSION SET PLSQL_WARNINGS = 'ENABLE:ALL';
Debugging Tools
- Oracle SQL Developer / SQLcl debugger
- VS Code Oracle Developer Tools extension
DBMS_HPROFhierarchical profiler,DBMS_PROFILER,PLSQL_TRACER- SQL trace with event 10046 /
DBMS_MONITOR
16. Common Built-in Packages
DBMS_OUTPUT
SQL
12345BEGIN
DBMS_OUTPUT.ENABLE(buffer_size => NULL); -- unlimited
DBMS_OUTPUT.PUT_LINE('Debug message');
END;
DBMS_SQL (Dynamic SQL)
SQL
123456789DECLARE
v_cursor NUMBER;
v_result NUMBER;
BEGIN
v_cursor := DBMS_SQL.OPEN_CURSOR;
-- Dynamic SQL operations
DBMS_SQL.CLOSE_CURSOR(v_cursor);
END;
UTL_FILE (File Operations)
SQL
12345678DECLARE
v_file UTL_FILE.FILE_TYPE;
BEGIN
v_file := UTL_FILE.FOPEN('OUTPUT_DIR', 'report.txt', 'W');
UTL_FILE.PUT_LINE(v_file, 'Report generated on ' || SYSDATE);
UTL_FILE.FCLOSE(v_file);
END;
APEX_JSON / JSON_OBJECT_T (JSON)
SQL
12345678DECLARE
jo JSON_OBJECT_T := JSON_OBJECT_T();
BEGIN
jo.put('name', 'Alice');
jo.put('salary', 60000);
DBMS_OUTPUT.PUT_LINE(jo.to_string);
END;
17. Notable Modern Features (19c – 23ai)
- SQL macros (
SCALAR/TABLE) — parameterized SQL fragments expanded at parse time (19c+). - Polymorphic Table Functions (PTFs) — table functions with dynamic row shape (18c+).
- Qualified expressions for records and collections (18c+).
- Iterator controls (
BY,VALUES OF,INDICES OF,PAIRS OF,WHILE) inFORloops (21c+). - Native JSON data type and
JSON_TRANSFORM(21c+). - Boolean as a SQL data type,
IF [NOT] EXISTSDDL,VALUESclause enhancements, direct joins inUPDATE/DELETE, schema-level privileges, JavaScript stored procedures via MLE (23ai). - SELECT without FROM (23ai):
SELECT 1+1; - Domain types (23ai) — reusable, named data type definitions usable in SQL and PL/SQL.
Additional Resources
- Oracle Database PL/SQL Language Reference (23ai)
- Oracle Database PL/SQL Packages and Types Reference
- Oracle Database Development Guide and Performance Tuning Guide
- Steven Feuerstein's Oracle PL/SQL Programming
Continue Learning
Discover more cheatsheets to boost your productivity