Skip to content

PL/SQL

Introduction to PL/SQL

SQL, as a comprehensive database language, includes procedural extensions for more complex operations, often referred to as Persistent Stored Modules (PSM) in the SQL standard. PL/SQL (Procedural Language/Structured Query Language) being Oracle's specific implementation.

These extensions allow for the definition of programmatic blocks that include iteration, conditional statements, and other constructs, akin to general-purpose programming languages.

Key Features and Benefits

PL/SQL extends SQL's capabilities and offers several advantages for application development:

  • Encapsulating Business Logic: It allows "business logic" to be stored directly within the database and executed from SQL statements.

  • Integration with SQL: It allows for the seamless use of all SQL statements, functions, and data types directly within its procedural code blocks.

  • Procedural Constructs: It adds standard programming constructs to SQL, including conditional statements (IF-THEN-ELSE, CASE), loops (FOR, WHILE), and variables.

  • Improved Performance: PL/SQL code is executed on the database server, which reduces network traffic between the application and the database. Entire blocks of statements can be sent to the server at once, minimizing communication overhead.

  • Modularity and Reusability: Code can be organized into reusable, named blocks such as procedures, functions, and packages which are stored on the database server. They can be invoked by multiple applications, reducing duplication of effort and improving software modularity

  • Robust Error Handling: It includes a comprehensive exception handling mechanism that allows developers to gracefully catch and manage runtime errors.

  • Enhanced Data Modeling: These procedural units can provide more complex types of derived data and enforce constraints beyond the capabilities of basic assertions and triggers.

PL/SQL Block Structure

Every PL/SQL program is constructed as a block. Blocks can be anonymous (unnamed) or named (as a procedure or function).

Anonymous Blocks / Compound Statements: These are the most fundamental building blocks, used to group multiple SQL and procedural statements logically.

  • They typically start with a BEGIN keyword and end with END.
  • They can optionally include a DECLARE section for defining local variables.
  • A specific form, BEGIN ATOMIC ... END, ensures that all statements within it execute as a single, indivisible transaction.

Basic Structure

sql
DECLARE
    -- Optional section: Declare variables, constants, cursors, and user-defined exceptions.
BEGIN
    -- Mandatory section: Contains the SQL and PL/SQL statements that are executed.
EXCEPTION
    -- Optional section: Contains handlers to manage runtime errors.
END;

Example: Anonymous Block

The following is a standard "Hello, World!" example. In Oracle development tools like SQL*Plus or SQL Developer, the forward slash (/) on a new line is used to execute the block.

sql
BEGIN
    DBMS_OUTPUT.PUT_LINE('Hello, World!');
END;
/

Note: The DELIMITER command is specific to the MySQL command-line client and is not used in Oracle's PL/SQL. The semicolon (;) terminates individual statements within the block, and the forward slash (/) executes the entire block.

Variables and Constants

Variables and constants can be declared within the DECLARE section of a PL/SQL block.

Declaring Variables

The basic syntax for declaring a variable is:

sql
variable_name datatype [NOT NULL] [:= initial_value];

A best practice is to anchor a variable's data type to a database column using the %TYPE attribute. This ensures the variable's data type automatically updates if the column definition changes.

sql
-- Variable 'v_last_name' will have the same data type as the 'last_name' column in the 'employees' table.
v_last_name employees.last_name%TYPE;

Declaring Constants

A constant holds a value that cannot be changed after it is initialized.

sql
constant_name CONSTANT datatype := value;
sql
DECLARE
    v_employee_id   employees.employee_id%TYPE := 100;
    v_employee_name employees.first_name%TYPE;
    c_tax_rate      CONSTANT NUMBER := 0.08;
BEGIN
    -- Select a value from a table into a variable
    SELECT first_name INTO v_employee_name
    FROM employees
    WHERE employee_id = v_employee_id;

    DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_employee_name);
    DBMS_OUTPUT.PUT_LINE('Tax Rate: ' || c_tax_rate);
END;
/

Control Structures

PL/SQL provides several structures to control the flow of execution.

Conditional Control

Conditional Statements: These blocks allow for conditional logic, enabling different actions based on specific conditions.

  • IF-THEN-ELSEIF-ELSE-END IF statements are used to execute a sequence of statements based on a boolean expression.
  • CASE statements provide another way to implement conditional logic, allowing different values to be returned based on specified conditions.

IF statements execute a sequence of statements based on a condition.

sql
IF condition1 THEN
    -- statements to execute if condition1 is TRUE
ELSIF condition2 THEN
    -- statements to execute if condition2 is TRUE
ELSE
    -- statements to execute if all previous conditions are FALSE
END IF;
sql
DECLARE
    v_score NUMBER := 85;
BEGIN
    IF v_score >= 90 THEN
        DBMS_OUTPUT.PUT_LINE('Grade: A');
    ELSIF v_score >= 80 THEN
        DBMS_OUTPUT.PUT_LINE('Grade: B');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Grade: C or below');
    END IF;
END;
/

CASE statements select an execution path from a set of alternatives.

sql
CASE selector
    WHEN value1 THEN statements1;
    WHEN value2 THEN statements2;
    ELSE else_statements;
END CASE;
sql
DECLARE
    v_grade CHAR(1) := 'B';
BEGIN
    CASE v_grade
        WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');
        WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good');
        WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Good');
        ELSE DBMS_OUTPUT.PUT_LINE('Needs Improvement');
    END CASE;
END;
/

Loop Control

Looping Statements: These blocks facilitate iteration over a set of statements or query results.

  • WHILE ... DO ... END WHILE: Executes a sequence of statements repeatedly as long as a specified boolean condition remains true.
  • REPEAT ... UNTIL ... END REPEAT: Executes a sequence of statements at least once, and then repeatedly until a specified boolean condition becomes true.
  • FOR ... AS CURSOR FOR ... DO ... END FOR: This is a cursor-based loop that iterates over the results of a query, executing a statement list for each tuple. Loops can be named, and a LEAVE statement can be used to exit a loop.

Simple Loop: Executes statements repeatedly until an EXIT condition is met.

sql
LOOP
    -- statements
    EXIT WHEN some_condition;
END LOOP;
sql
DECLARE
    v_counter NUMBER := 1;
BEGIN
    LOOP
        DBMS_OUTPUT.PUT_LINE('Counter: ' || v_counter);
        v_counter := v_counter + 1;
        EXIT WHEN v_counter > 5;
    END LOOP;
END;
/

WHILE Loop: Executes as long as a specified condition is TRUE.

sql
WHILE condition LOOP
    -- statements
END LOOP;
sql
DECLARE
    v_counter NUMBER := 5;
BEGIN
    WHILE v_counter > 0 LOOP
        DBMS_OUTPUT.PUT_LINE('Countdown: ' || v_counter);
        v_counter := v_counter - 1;
    END LOOP;
END;
/

FOR Loop: Executes for a fixed number of iterations. The loop counter is implicitly declared.

sql
FOR counter_variable IN [REVERSE] lower_bound..upper_bound LOOP
    -- statements
END LOOP;
sql
BEGIN
    FOR i IN 1..5 LOOP
        DBMS_OUTPUT.PUT_LINE('Iteration: ' || i);
    END LOOP;
END;
/

Error Handling

The EXCEPTION block is used to catch and handle errors that occur during the execution of the BEGIN block.

sql
DECLARE
    v_employee_name employees.first_name%TYPE;
BEGIN
    -- This query will fail if employee_id 999 does not exist.
    SELECT first_name INTO v_employee_name
    FROM employees
    WHERE employee_id = 999;

    DBMS_OUTPUT.PUT_LINE('Employee: ' || v_employee_name);

EXCEPTION
    -- This specific exception handler catches the error for "no rows found".
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Error: No employee was found with that ID.');

    -- The OTHERS handler catches all other unhandled exceptions.
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('An unexpected error occurred.');
        -- SQLERRM and SQLCODE are built-in functions to get error details.
        DBMS_OUTPUT.PUT_LINE('Error Code: ' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('Error Message: ' || SQLERRM);
END;
/

Basic Practice

It's important to note that PL/SQL is specific to Oracle databases. MySQL, which has its own, very similar procedural language. The following examples use the correct syntax for your MySQL database.

A Note on DELIMITER

When writing multi-statement commands in the mysql> client, you need to temporarily change the delimiter from the default semicolon (;) so the client doesn't end your procedure definition prematurely. We'll use // as the temporary delimiter.

Stored Procedures

Stored procedures are reusable blocks of code that you can execute on demand.

Question 1: Create a procedure that accepts a major name and returns all students in that major.

Answer: This procedure takes a major name as an IN (input) parameter and uses it in a SELECT statement.

sql
DELIMITER //

CREATE PROCEDURE GetStudentsByMajor(IN major_name VARCHAR(100))
BEGIN
    SELECT *
    FROM Students
    WHERE Major = major_name;
END //

DELIMITER ;

To run this procedure:

sql
CALL GetStudentsByMajor('Computer Science');

Question 2: Create a procedure that takes a Course ID and returns the number of enrolled students.

Answer: This procedure uses an IN parameter for the Course ID and an OUT (output) parameter to return the count.

sql
DELIMITER //

CREATE PROCEDURE GetEnrollmentCount(IN c_id INT, OUT student_count INT)
BEGIN
    SELECT COUNT(*) INTO student_count
    FROM Enrollment
    WHERE CourseID = c_id;
END //

DELIMITER ;

To run this procedure:

sql
CALL GetEnrollmentCount(201, @count);
SELECT @count AS 'Students in Course 201';

Functions

Functions are similar to procedures but must return a single value.

Question: Create a function that takes a StudentID and returns the student's full name.

Answer: This function is useful for embedding in other queries to get formatted data.

sql
DELIMITER //

CREATE FUNCTION GetStudentName(s_id INT)
RETURNS VARCHAR(100)
DETERMINISTIC
BEGIN
    DECLARE student_name VARCHAR(100);
    SELECT Name INTO student_name
    FROM Students
    WHERE StudentID = s_id;
    RETURN student_name;
END //

DELIMITER ;

To use this function:

sql
SELECT GetStudentName(1001);

Triggers

Triggers are special procedures that automatically run in response to DML events (INSERT, UPDATE, DELETE).

Question: Create an audit table and a trigger that logs an entry whenever a student's major is updated.

Answer: This is a common use case for triggers, allowing you to track changes to important data.

Step 1: Create the audit table.

sql
CREATE TABLE StudentAudit (
    AuditID INT AUTO_INCREMENT PRIMARY KEY,
    StudentID INT,
    OldMajor VARCHAR(100),
    NewMajor VARCHAR(100),
    ChangeTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Step 2: Create the trigger.

This trigger will fire after any UPDATE on the Students table.

sql
DELIMITER //

CREATE TRIGGER AfterMajorUpdate
AFTER UPDATE ON Students
FOR EACH ROW
BEGIN
    IF OLD.Major <> NEW.Major THEN
        INSERT INTO StudentAudit(StudentID, OldMajor, NewMajor)
        VALUES (OLD.StudentID, OLD.Major, NEW.Major);
    END IF;
END //

DELIMITER ;

To test this trigger:

sql
-- First, update a student's major
UPDATE Students SET Major = 'Computer Engineering' WHERE StudentID = 1001;

-- Now, check the audit table to see the logged change
SELECT * FROM StudentAudit;

Made with ❤️ for students, by a fellow learner.