Write, Run & Share PL/SQL code online using OneCompiler's Oracle PL/SQL online editor for free. It's one of the robust, feature-rich online editors for Oracle PL/SQL running on Oracle 23c. Getting started with the OneCompiler's PL/SQL editor is really simple and pretty fast. The editor shows sample boilerplate code when you choose language as 'PL/SQL' and start writing code to learn and test online without worrying about tedious process of installation.
PL/SQL (Procedural Language/SQL) is Oracle's procedural extension for SQL. It combines SQL data manipulation with procedural programming constructs like variables, conditions, loops, and exception handling. PL/SQL code runs directly within the Oracle Database engine, enabling efficient execution of complex business logic without network round trips.
Anonymous blocks are unnamed PL/SQL code units that execute immediately. Every PL/SQL block has three sections: DECLARE (optional) for variables, BEGIN for executable statements, and EXCEPTION (optional) for error handling. Use anonymous blocks for quick scripts, testing, and one-time operations that don't need to be stored in the database.
-- Basic anonymous block DECLARE message VARCHAR2(100) := 'Hello, World!'; BEGIN DBMS_OUTPUT.PUT_LINE(message); END; / -- Block with calculations DECLARE x NUMBER := 10; y NUMBER := 5; result NUMBER; BEGIN result := x + y; DBMS_OUTPUT.PUT_LINE('Sum: ' || result); result := x * y; DBMS_OUTPUT.PUT_LINE('Product: ' || result); END; /
PL/SQL supports various data types including NUMBER, VARCHAR2, DATE, BOOLEAN, and composite types. Variables must be declared in the DECLARE section before use. Use %TYPE to inherit a column's data type and %ROWTYPE for entire row structures, ensuring your code stays synchronized with table schema changes.
DECLARE emp_name VARCHAR2(50) := 'John Smith'; emp_salary NUMBER(10,2) := 75000.00; hire_date DATE := SYSDATE; is_active BOOLEAN := TRUE; counter PLS_INTEGER := 0; BEGIN DBMS_OUTPUT.PUT_LINE('Employee: ' || emp_name); DBMS_OUTPUT.PUT_LINE('Salary: $' || emp_salary); DBMS_OUTPUT.PUT_LINE('Hired: ' || TO_CHAR(hire_date, 'YYYY-MM-DD')); IF is_active THEN DBMS_OUTPUT.PUT_LINE('Status: Active'); END IF; END; /
PL/SQL provides IF-THEN-ELSE and CASE statements for conditional logic. IF statements can be nested and combined with ELSIF for multiple conditions. CASE statements offer cleaner syntax when comparing a single expression against multiple values. Both are essential for implementing business rules and decision-making logic.
DECLARE score NUMBER := 85; grade VARCHAR2(1); BEGIN -- IF-THEN-ELSIF-ELSE IF score >= 90 THEN grade := 'A'; ELSIF score >= 80 THEN grade := 'B'; ELSIF score >= 70 THEN grade := 'C'; ELSIF score >= 60 THEN grade := 'D'; ELSE grade := 'F'; END IF; DBMS_OUTPUT.PUT_LINE('Score: ' || score || ', Grade: ' || grade); END; / -- CASE expression DECLARE day_num NUMBER := TO_CHAR(SYSDATE, 'D'); day_name VARCHAR2(20); BEGIN day_name := CASE day_num WHEN 1 THEN 'Sunday' WHEN 2 THEN 'Monday' WHEN 3 THEN 'Tuesday' WHEN 4 THEN 'Wednesday' WHEN 5 THEN 'Thursday' WHEN 6 THEN 'Friday' WHEN 7 THEN 'Saturday' END; DBMS_OUTPUT.PUT_LINE('Today is ' || day_name); END; /
PL/SQL offers three loop types: basic LOOP with EXIT, WHILE loop, and FOR loop. Basic loops continue until an explicit EXIT condition. WHILE loops check the condition before each iteration. FOR loops are ideal for known iteration counts and automatically declare and increment the loop variable.
-- FOR loop (most common) BEGIN FOR i IN 1..5 LOOP DBMS_OUTPUT.PUT_LINE('Iteration: ' || i); END LOOP; END; / -- FOR loop in reverse BEGIN FOR i IN REVERSE 1..5 LOOP DBMS_OUTPUT.PUT_LINE('Countdown: ' || i); END LOOP; END; / -- WHILE loop DECLARE counter NUMBER := 1; BEGIN WHILE counter <= 5 LOOP DBMS_OUTPUT.PUT_LINE('Counter: ' || counter); counter := counter + 1; END LOOP; END; / -- Basic LOOP with EXIT DECLARE n NUMBER := 0; BEGIN LOOP n := n + 1; DBMS_OUTPUT.PUT_LINE('N = ' || n); EXIT WHEN n >= 5; END LOOP; END; /
Functions are named PL/SQL blocks that return a single value. They can accept parameters and are called from SQL statements or other PL/SQL code. Functions must have a RETURN clause specifying the return data type. Use functions for calculations, data transformations, and reusable logic that produces a single result.
-- Create a function CREATE OR REPLACE FUNCTION calculate_bonus( salary IN NUMBER, performance_rating IN NUMBER ) RETURN NUMBER IS bonus NUMBER; BEGIN bonus := salary * (performance_rating / 100); RETURN bonus; END calculate_bonus; / -- Use the function DECLARE emp_salary NUMBER := 50000; rating NUMBER := 15; emp_bonus NUMBER; BEGIN emp_bonus := calculate_bonus(emp_salary, rating); DBMS_OUTPUT.PUT_LINE('Salary: $' || emp_salary); DBMS_OUTPUT.PUT_LINE('Performance Rating: ' || rating || '%'); DBMS_OUTPUT.PUT_LINE('Bonus: $' || emp_bonus); END; /
Procedures are named PL/SQL blocks that perform actions but don't return values directly. They can have IN (input), OUT (output), and IN OUT (bidirectional) parameters. Procedures are ideal for complex operations, data modifications, and business processes that perform multiple steps or return multiple values through OUT parameters.
-- Create a procedure CREATE OR REPLACE PROCEDURE greet_user( user_name IN VARCHAR2, greeting OUT VARCHAR2 ) IS BEGIN greeting := 'Hello, ' || user_name || '! Welcome to PL/SQL.'; END greet_user; / -- Call the procedure DECLARE message VARCHAR2(100); BEGIN greet_user('Alice', message); DBMS_OUTPUT.PUT_LINE(message); END; / -- Procedure with IN OUT parameter CREATE OR REPLACE PROCEDURE double_value( num IN OUT NUMBER ) IS BEGIN num := num * 2; END double_value; / DECLARE my_number NUMBER := 25; BEGIN DBMS_OUTPUT.PUT_LINE('Before: ' || my_number); double_value(my_number); DBMS_OUTPUT.PUT_LINE('After: ' || my_number); END; /
Exception handling catches and manages runtime errors gracefully. The EXCEPTION section catches specific named exceptions (like NO_DATA_FOUND, TOO_MANY_ROWS) or uses WHEN OTHERS for all errors. SQLERRM and SQLCODE provide error details. Proper exception handling prevents program crashes and enables meaningful error messages or recovery actions.
-- Basic exception handling DECLARE result NUMBER; BEGIN result := 10 / 0; DBMS_OUTPUT.PUT_LINE('Result: ' || result); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Error: Cannot divide by zero!'); END; / -- Multiple exception handlers DECLARE v_name VARCHAR2(10); BEGIN v_name := 'This string is too long for the variable'; EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE('Error: Value too large for variable'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM); DBMS_OUTPUT.PUT_LINE('Error Code: ' || SQLCODE); END; / -- User-defined exception DECLARE age NUMBER := 15; invalid_age EXCEPTION; BEGIN IF age < 18 THEN RAISE invalid_age; END IF; DBMS_OUTPUT.PUT_LINE('Access granted'); EXCEPTION WHEN invalid_age THEN DBMS_OUTPUT.PUT_LINE('Error: Must be 18 or older'); END; /
Cursors process query results row by row. Implicit cursors are created automatically for single-row queries. Explicit cursors give you control over multi-row result sets with OPEN, FETCH, and CLOSE operations. Cursor FOR loops provide the cleanest syntax by automatically handling cursor lifecycle. Use cursors when you need to process records individually.
-- Implicit cursor with SQL%ROWCOUNT BEGIN UPDATE employees SET salary = salary * 1.1 WHERE dept = 'Sales'; DBMS_OUTPUT.PUT_LINE('Rows updated: ' || SQL%ROWCOUNT); ROLLBACK; END; / -- Explicit cursor with FOR loop (recommended) DECLARE CURSOR emp_cursor IS SELECT empId, name, salary FROM employees WHERE dept = 'Sales'; BEGIN FOR emp_rec IN emp_cursor LOOP DBMS_OUTPUT.PUT_LINE(emp_rec.name || ': $' || emp_rec.salary); END LOOP; END; / -- Cursor with parameters DECLARE CURSOR dept_cursor(p_dept VARCHAR2) IS SELECT name, salary FROM employees WHERE dept = p_dept; BEGIN DBMS_OUTPUT.PUT_LINE('Sales Department:'); FOR emp IN dept_cursor('Sales') LOOP DBMS_OUTPUT.PUT_LINE(' ' || emp.name || ': $' || emp.salary); END LOOP; END; /
Records group related data items into a single unit, similar to structs. Collections (VARRAYs, nested tables, associative arrays) store multiple elements of the same type. Use %ROWTYPE to create records matching table structure. Collections are essential for bulk operations and passing multiple values between procedures.
-- Record with %ROWTYPE DECLARE emp_rec employees%ROWTYPE; BEGIN SELECT * INTO emp_rec FROM employees WHERE empId = 1; DBMS_OUTPUT.PUT_LINE('Name: ' || emp_rec.name); DBMS_OUTPUT.PUT_LINE('Dept: ' || emp_rec.dept); DBMS_OUTPUT.PUT_LINE('Salary: $' || emp_rec.salary); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Employee not found'); END; / -- Associative array (index-by table) DECLARE TYPE name_table IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER; names name_table; BEGIN names(1) := 'Alice'; names(2) := 'Bob'; names(3) := 'Charlie'; FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE('Person ' || i || ': ' || names(i)); END LOOP; END; /
Dynamic SQL builds and executes SQL statements at runtime using EXECUTE IMMEDIATE. It's useful when table names, column names, or entire queries are determined dynamically. Bind variables (using placeholders) should be used to prevent SQL injection and improve performance. Dynamic SQL enables flexible, generic database operations.
-- Simple dynamic SQL DECLARE table_name VARCHAR2(30) := 'employees'; row_count NUMBER; BEGIN EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || table_name INTO row_count; DBMS_OUTPUT.PUT_LINE('Rows in ' || table_name || ': ' || row_count); END; / -- Dynamic SQL with bind variables DECLARE dept_name VARCHAR2(30) := 'Sales'; emp_count NUMBER; BEGIN EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM employees WHERE dept = :1' INTO emp_count USING dept_name; DBMS_OUTPUT.PUT_LINE('Employees in ' || dept_name || ': ' || emp_count); END; /
Bulk operations (BULK COLLECT and FORALL) dramatically improve performance by reducing context switches between PL/SQL and SQL engines. BULK COLLECT fetches multiple rows into collections in one operation. FORALL executes DML statements for all collection elements in a single pass. Use bulk operations whenever processing large datasets.
-- BULK COLLECT example DECLARE TYPE name_list IS TABLE OF employees.name%TYPE; names name_list; BEGIN SELECT name BULK COLLECT INTO names FROM employees; DBMS_OUTPUT.PUT_LINE('Total employees: ' || names.COUNT); FOR i IN 1..names.COUNT LOOP DBMS_OUTPUT.PUT_LINE(i || '. ' || names(i)); END LOOP; END; /