Control Flow


Control flow dictates the order in which statements are executed within a PL/SQL program. Understanding and effectively utilizing control flow constructs is fundamental for writing efficient, logical, and maintainable Oracle PL/SQL code. This section covers various techniques for managing the flow of execution, from conditional branching to iterative processing.

Conditional Logic

Conditional logic allows your PL/SQL programs to make decisions and execute different blocks of code based on whether specific conditions are met. This is a cornerstone of dynamic and responsive applications.

IF-THEN-ELSE Statements

The IF-THEN-ELSE statement is the most common and basic form of conditional control in Oracle PL/SQL. It allows you to execute a sequence of statements only if a specified condition is true. You can also provide alternative actions if the condition is false. This is essential for implementing business rules and data validation.

Note: Oracle PL/SQL IF statement, conditional branching, THEN ELSE, ELSIF, PL/SQL decision making

Example 1: Basic IF-THEN Statement

DECLARE
    v_score NUMBER := 85; -- Declare and initialize a variable for a student's score
BEGIN
    -- Check if the score is greater than or equal to 70 for a passing grade
    IF v_score >= 70 THEN
        DBMS_OUTPUT.PUT_LINE('Congratulations! You passed the exam.'); -- Output message if condition is true
    END IF;
END;
/

Explanation

This example demonstrates a simple IF-THEN statement. It declares a numeric variable v_score and initializes it. The IF v_score >= 70 THEN condition checks if the score is 70 or higher. If this condition evaluates to TRUE, the message "Congratulations! You passed the exam." is displayed using DBMS_OUTPUT.PUT_LINE. If the condition is FALSE, the code within the IF block is skipped.

Example 2: IF-THEN-ELSE Statement

DECLARE
    v_age NUMBER := 17; -- Declare and initialize a variable for a person's age
BEGIN
    -- Check if the age is 18 or older to determine eligibility
    IF v_age >= 18 THEN
        DBMS_OUTPUT.PUT_LINE('You are old enough to vote.'); -- Message for eligible voters
    ELSE
        DBMS_OUTPUT.PUT_LINE('You are not yet old enough to vote.'); -- Message for ineligible voters
    END IF;
END;
/

Explanation

This example showcases an IF-THEN-ELSE statement. It checks if v_age is 18 or greater. If the condition is TRUE, the message "You are old enough to vote." is printed. If the condition is FALSE (meaning v_age is less than 18), the code within the ELSE block is executed, printing "You are not yet old enough to vote." This allows for two distinct execution paths based on the condition.

Example 3: IF-ELSIF-ELSE Statement for Multiple Conditions

DECLARE
    v_grade_percentage NUMBER := 92; -- Student's percentage grade
    v_letter_grade VARCHAR2(1); -- Variable to store the letter grade
BEGIN
    -- Determine the letter grade based on percentage
    IF v_grade_percentage >= 90 THEN
        v_letter_grade := 'A'; -- Assign 'A' for 90% and above
    ELSIF v_grade_percentage >= 80 THEN
        v_letter_grade := 'B'; -- Assign 'B' for 80-89%
    ELSIF v_grade_percentage >= 70 THEN
        v_letter_grade := 'C'; -- Assign 'C' for 70-79%
    ELSIF v_grade_percentage >= 60 THEN
        v_letter_grade := 'D'; -- Assign 'D' for 60-69%
    ELSE
        v_letter_grade := 'F'; -- Assign 'F' for below 60%
    END IF;

    DBMS_OUTPUT.PUT_LINE('Your letter grade is: ' || v_letter_grade); -- Display the calculated letter grade
END;
/

Explanation

This example demonstrates the use of ELSIF for handling multiple conditions. The code sequentially checks the v_grade_percentage against various thresholds. As soon as a condition evaluates to TRUE, the corresponding THEN block is executed, v_letter_grade is assigned, and the rest of the ELSIF and ELSE conditions are skipped. If none of the IF or ELSIF conditions are met, the ELSE block is executed, assigning 'F'. This is ideal for scenarios with several possible outcomes.

Example 4: Nested IF Statements

DECLARE
    v_is_senior BOOLEAN := TRUE; -- Flag for senior citizen status
    v_has_discount_card BOOLEAN := TRUE; -- Flag for discount card
BEGIN
    -- Check for senior discount eligibility
    IF v_is_senior THEN
        DBMS_OUTPUT.PUT_LINE('Eligible for senior discount.');
        -- Further check for additional discount if they also have a discount card
        IF v_has_discount_card THEN
            DBMS_OUTPUT.PUT_LINE('Also eligible for an additional discount with discount card.');
        END IF;
    ELSE
        DBMS_OUTPUT.PUT_LINE('Not eligible for senior discount.');
    END IF;
END;
/

Explanation

This example illustrates nested IF statements, where one IF statement is contained within another. The outer IF checks for v_is_senior. If true, it prints a message and then proceeds to the inner IF to check v_has_discount_card. This allows for more granular decision-making based on a hierarchy of conditions.

Example 5: IF with NULL Values

DECLARE
    v_value_a NUMBER := 10;
    v_value_b NUMBER := NULL; -- A variable with a NULL value
BEGIN
    -- Check conditions involving NULL
    IF v_value_a IS NOT NULL THEN
        DBMS_OUTPUT.PUT_LINE('v_value_a is not NULL.');
    END IF;

    IF v_value_b IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('v_value_b is NULL.');
    END IF;

    -- Important: NULL comparisons result in UNKNOWN, not TRUE or FALSE
    IF v_value_a = v_value_b THEN -- This condition will be UNKNOWN
        DBMS_OUTPUT.PUT_LINE('This line will not be executed (v_value_a = v_value_b is UNKNOWN).');
    END IF;

    IF v_value_a != v_value_b THEN -- This condition will also be UNKNOWN
        DBMS_OUTPUT.PUT_LINE('This line will also not be executed (v_value_a != v_value_b is UNKNOWN).');
    END IF;
END;
/

Explanation

This example demonstrates how IF statements interact with NULL values. It's crucial to use IS NULL or IS NOT NULL to check for the presence or absence of a value, as direct comparisons (= or !=) involving NULL always result in UNKNOWN, preventing the IF condition from evaluating to TRUE. The example highlights that UNKNOWN results in the IF block being skipped.

 

CASE Statements and Expressions

The CASE statement and CASE expression provide a more structured and readable way to handle multiple conditional branches, especially when you are testing a single value against several possible options. They are often a cleaner alternative to complex IF-ELSIF-ELSE constructs.

Note: Oracle PL/SQL CASE statement, CASE expression, simple CASE, searched CASE, multi-way branching, SQL CASE

Example 1: Simple CASE Statement

DECLARE
    v_day_number NUMBER := 3; -- Represents a day of the week (1=Monday, 2=Tuesday, etc.)
    v_day_name VARCHAR2(10); -- To store the corresponding day name
BEGIN
    -- Determine the day name based on the day number
    CASE v_day_number
        WHEN 1 THEN
            v_day_name := 'Monday';
        WHEN 2 THEN
            v_day_name := 'Tuesday';
        WHEN 3 THEN
            v_day_name := 'Wednesday';
        WHEN 4 THEN
            v_day_name := 'Thursday';
        WHEN 5 THEN
            v_day_name := 'Friday';
        WHEN 6 THEN
            v_day_name := 'Saturday';
        WHEN 7 THEN
            v_day_name := 'Sunday';
        ELSE
            v_day_name := 'Invalid Day'; -- Handle cases where v_day_number is not 1-7
    END CASE;
    DBMS_OUTPUT.PUT_LINE('Today is: ' || v_day_name); -- Display the determined day name
END;
/

Explanation

This example uses a simple CASE statement to map a numeric v_day_number to its corresponding v_day_name. The CASE statement evaluates v_day_number and executes the WHEN clause whose value matches v_day_number. If no WHEN clause matches, the ELSE clause is executed. This provides a clean way to handle a fixed set of possibilities.

Example 2: Searched CASE Statement

DECLARE
    v_temperature NUMBER := 25; -- Current temperature in Celsius
    v_weather_outlook VARCHAR2(20); -- Description of the weather
BEGIN
    -- Determine weather outlook based on temperature ranges
    CASE
        WHEN v_temperature < 0 THEN
            v_weather_outlook := 'Freezing Cold';
        WHEN v_temperature BETWEEN 0 AND 10 THEN
            v_weather_outlook := 'Very Cold';
        WHEN v_temperature BETWEEN 11 AND 20 THEN
            v_weather_outlook := 'Cool';
        WHEN v_temperature BETWEEN 21 AND 30 THEN
            v_weather_outlook := 'Pleasant';
        WHEN v_temperature > 30 THEN
            v_weather_outlook := 'Hot';
        ELSE
            v_weather_outlook := 'Unknown Temperature';
    END CASE;
    DBMS_OUTPUT.PUT_LINE('Weather outlook: ' || v_weather_outlook);
END;
/

Explanation

This example demonstrates a searched CASE statement, which is more flexible than the simple CASE. Instead of comparing a single expression, each WHEN clause has its own boolean condition. The first WHEN clause whose condition evaluates to TRUE is executed. This is particularly useful for evaluating different ranges or complex conditions.

Example 3: CASE Expression (within a SELECT statement)

SELECT
    employee_id,
    first_name,
    salary,
    CASE
        WHEN salary < 5000 THEN 'Low Salary'
        WHEN salary BETWEEN 5000 AND 10000 THEN 'Medium Salary'
        WHEN salary > 10000 THEN 'High Salary'
        ELSE 'Undefined'
    END AS salary_category -- The CASE expression becomes a new column
FROM
    employees
WHERE
    ROWNUM <= 5; -- Limiting for example purposes

Explanation

This example shows a CASE expression used within a SELECT statement in SQL (which is often used within PL/SQL blocks when querying data). A CASE expression returns a single value based on the conditions. Here, it creates a new column salary_category that categorizes each employee's salary into "Low Salary", "Medium Salary", or "High Salary". This is incredibly powerful for data categorization and reporting.

Example 4: CASE Statement with NULL Handling

DECLARE
    v_user_status VARCHAR2(10) := NULL; -- User status can be 'ACTIVE', 'INACTIVE', or NULL
    v_message VARCHAR2(50);
BEGIN
    -- Handle user status, including NULL
    CASE v_user_status
        WHEN 'ACTIVE' THEN
            v_message := 'User is currently active.';
        WHEN 'INACTIVE' THEN
            v_message := 'User is currently inactive.';
        WHEN NULL THEN -- Explicitly handle NULL for simple CASE
            v_message := 'User status is unknown.';
        ELSE
            v_message := 'Invalid user status encountered.';
    END CASE;
    DBMS_OUTPUT.PUT_LINE(v_message);

    v_user_status := 'ACTIVE'; -- Change status for another test
    CASE v_user_status
        WHEN 'ACTIVE' THEN
            v_message := 'User is currently active.';
        WHEN 'INACTIVE' THEN
            v_message := 'User is currently inactive.';
        WHEN NULL THEN
            v_message := 'User status is unknown.';
        ELSE
            v_message := 'Invalid user status encountered.';
    END CASE;
    DBMS_OUTPUT.PUT_LINE(v_message);

    v_user_status := 'SUSPENDED'; -- Test an 'ELSE' case
    CASE v_user_status
        WHEN 'ACTIVE' THEN
            v_message := 'User is currently active.';
        WHEN 'INACTIVE' THEN
            v_message := 'User is currently inactive.';
        WHEN NULL THEN
            v_message := 'User status is unknown.';
        ELSE
            v_message := 'Invalid user status encountered.';
    END CASE;
    DBMS_OUTPUT.PUT_LINE(v_message);
END;
/

Explanation

This example highlights how to handle NULL values within a simple CASE statement. Unlike IF statements where NULL comparisons result in UNKNOWN, a WHEN NULL clause in a simple CASE will explicitly match if the expression being evaluated is NULL. This provides clear handling for missing or undefined data points.

Example 5: CASE Statement with No ELSE Clause (and potential NO_DATA_FOUND)

DECLARE
    v_product_category VARCHAR2(20) := 'Electronics';
    v_delivery_time VARCHAR2(20);
BEGIN
    -- If no WHEN clause matches and there's no ELSE, a CASE_NOT_FOUND exception is raised
    BEGIN
        CASE v_product_category
            WHEN 'Books' THEN
                v_delivery_time := '3-5 business days';
            WHEN 'Food' THEN
                v_delivery_time := '1-2 business days';
            WHEN 'Apparel' THEN
                v_delivery_time := '5-7 business days';
            -- No ELSE clause intentionally to demonstrate exception
        END CASE;
        DBMS_OUTPUT.PUT_LINE('Delivery time for ' || v_product_category || ': ' || v_delivery_time);
    EXCEPTION
        WHEN CASE_NOT_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('Error: Product category "' || v_product_category || '" not found in delivery logic.');
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('An unexpected error occurred.');
    END;

    v_product_category := 'Food'; -- Test a matching case
    BEGIN
        CASE v_product_category
            WHEN 'Books' THEN
                v_delivery_time := '3-5 business days';
            WHEN 'Food' THEN
                v_delivery_time := '1-2 business days';
            WHEN 'Apparel' THEN
                v_delivery_time := '5-7 business days';
        END CASE;
        DBMS_OUTPUT.PUT_LINE('Delivery time for ' || v_product_category || ': ' || v_delivery_time);
    EXCEPTION
        WHEN CASE_NOT_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('Error: Product category "' || v_product_category || '" not found in delivery logic.');
    END;
END;
/

Explanation

This example demonstrates the behavior of a CASE statement when no ELSE clause is provided and none of the WHEN conditions match. In such a scenario, Oracle PL/SQL raises a CASE_NOT_FOUND exception. It's crucial to either include an ELSE clause to handle all possible values or implement exception handling (EXCEPTION WHEN CASE_NOT_FOUND) to gracefully manage unmatched cases and prevent runtime errors.

 

Loops

Loops are fundamental control structures that allow a block of code to be executed repeatedly. This is essential for iterating over collections, processing sets of data, or repeating actions until a certain condition is met. Oracle PL/SQL provides several types of loops to cater to different iteration requirements.

Note: Oracle PL/SQL loops, iterative processing, Basic LOOP, WHILE LOOP, FOR LOOP, REVERSE FOR LOOP, loop control, EXIT, CONTINUE

Basic Loop

The LOOP...END LOOP construct is the simplest form of loop in Oracle PL/SQL. It creates an indefinite loop that will execute indefinitely unless an EXIT statement is encountered. This makes it suitable for situations where the exit condition is determined dynamically within the loop's body.

Note: PL/SQL infinite loop, EXIT statement, LOOP END LOOP, basic loop structure

Example 1: Basic Loop with EXIT WHEN

DECLARE
    v_counter NUMBER := 1; -- Initialize a counter variable
BEGIN
    DBMS_OUTPUT.PUT_LINE('Starting basic loop...');
    LOOP
        DBMS_OUTPUT.PUT_LINE('Iteration: ' || v_counter); -- Display the current iteration
        v_counter := v_counter + 1; -- Increment the counter
        EXIT WHEN v_counter > 5; -- Exit the loop when counter exceeds 5
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Basic loop finished.');
END;
/

Explanation

This example demonstrates a basic LOOP with an EXIT WHEN clause. The loop continuously increments v_counter and prints its value. The EXIT WHEN v_counter > 5 statement acts as the termination condition; when v_counter becomes 6, the loop is exited, and execution continues after END LOOP.

Example 2: Basic Loop with Conditional EXIT

DECLARE
    v_balance NUMBER := 100; -- Initial balance
    v_withdrawal_amount NUMBER := 20; -- Amount to withdraw in each iteration
BEGIN
    DBMS_OUTPUT.PUT_LINE('Initial Balance: $' || v_balance);
    LOOP
        IF v_balance < v_withdrawal_amount THEN
            DBMS_OUTPUT.PUT_LINE('Insufficient funds to withdraw $' || v_withdrawal_amount || '.');
            EXIT; -- Exit if balance is too low
        END IF;

        v_balance := v_balance - v_withdrawal_amount; -- Deduct withdrawal amount
        DBMS_OUTPUT.PUT_LINE('Withdrew $' || v_withdrawal_amount || '. Remaining Balance: $' || v_balance);
        EXIT WHEN v_balance <= 0; -- Exit if balance drops to 0 or below
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Final Balance: $' || v_balance);
END;
/

Explanation

This example shows a basic LOOP with an IF statement inside to check for an exit condition. It simulates withdrawals. The loop exits if v_balance falls below v_withdrawal_amount (due to IF v_balance < v_withdrawal_amount THEN EXIT;) or if the v_balance drops to zero or less (due to EXIT WHEN v_balance <= 0). This demonstrates flexible exit conditions.

Example 3: Basic Loop with Nested Loop and Label

DECLARE
    i NUMBER := 1;
    j NUMBER;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Starting nested loops:');
    <<outer_loop>> -- Label for the outer loop
    LOOP
        DBMS_OUTPUT.PUT_LINE('  Outer Loop Iteration: ' || i);
        j := 1;
        <<inner_loop>> -- Label for the inner loop
        LOOP
            DBMS_OUTPUT.PUT_LINE('    Inner Loop Iteration: ' || j);
            j := j + 1;
            EXIT inner_loop WHEN j > 3; -- Exit the inner loop
        END LOOP inner_loop; -- Referencing the inner loop label explicitly
        i := i + 1;
        EXIT outer_loop WHEN i > 2; -- Exit the outer loop
    END LOOP outer_loop; -- Referencing the outer loop label explicitly
    DBMS_OUTPUT.PUT_LINE('Nested loops finished.');
END;
/

Explanation

This example demonstrates nested basic loops and the use of loop labels. Labels (e.g., <<outer_loop>>, <<inner_loop>>) are used to uniquely identify loops, especially useful for EXIT statements to specify which loop to exit. EXIT inner_loop WHEN j > 3; exits only the inner loop, while EXIT outer_loop WHEN i > 2; exits the entire outer loop.

Example 4: Basic Loop Processing a Cursor (FETCH loop)

DECLARE
    CURSOR emp_cursor IS
        SELECT employee_id, first_name, last_name
        FROM employees
        WHERE ROWNUM <= 3; -- Limiting for example
    v_emp_rec emp_cursor%ROWTYPE; -- Record type to hold fetched row
BEGIN
    DBMS_OUTPUT.PUT_LINE('Fetching employee data:');
    OPEN emp_cursor; -- Open the cursor to fetch data
    LOOP
        FETCH emp_cursor INTO v_emp_rec; -- Fetch one row into the record variable
        EXIT WHEN emp_cursor%NOTFOUND; -- Exit when no more rows are found

        DBMS_OUTPUT.PUT_LINE('Employee ID: ' || v_emp_rec.employee_id ||
                             ', Name: ' || v_emp_rec.first_name || ' ' || v_emp_rec.last_name);
    END LOOP;
    CLOSE emp_cursor; -- Close the cursor
    DBMS_OUTPUT.PUT_LINE('Finished fetching employee data.');
END;
/

Explanation

This is a common and practical use of a basic loop: iterating over a result set from a cursor. The LOOP continuously FETCHes rows from emp_cursor into v_emp_rec. The EXIT WHEN emp_cursor%NOTFOUND; condition checks if the last FETCH operation failed to retrieve a row, indicating that all rows have been processed, and then exits the loop. This pattern is fundamental for row-by-row processing in PL/SQL.

Example 5: Basic Loop with CONTINUE

DECLARE
    v_number NUMBER := 0;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Demonstrating CONTINUE:');
    LOOP
        v_number := v_number + 1;
        IF MOD(v_number, 2) = 0 THEN -- Check if the number is even
            DBMS_OUTPUT.PUT_LINE('  Skipping even number: ' || v_number);
            CONTINUE; -- Skip the rest of the current iteration and go to the next
        END IF;

        DBMS_OUTPUT.PUT_LINE('  Processing odd number: ' || v_number);
        EXIT WHEN v_number >= 7; -- Exit when number reaches 7 (after processing)
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Loop with CONTINUE finished.');
END;
/

Explanation

This example illustrates the CONTINUE statement within a basic loop. When v_number is even, CONTINUE is executed. This immediately skips the remaining statements in the current iteration of the loop (i.e., DBMS_OUTPUT.PUT_LINE(' Processing odd number: ' || v_number); is skipped for even numbers) and proceeds to the next iteration. This is useful when you want to bypass certain operations based on a condition within the loop.

 

WHILE Loop

A WHILE loop in Oracle PL/SQL executes a sequence of statements repeatedly as long as a specified condition remains true. The condition is evaluated at the beginning of each iteration, meaning the loop body might not execute at all if the condition is initially false. This is ideal when the number of iterations is not known in advance but depends on a dynamic condition.

Note: PL/SQL WHILE loop, loop with condition, pre-test loop, iterative processing, condition at start

Example 1: Basic WHILE Loop

DECLARE
    v_counter NUMBER := 1; -- Initialize loop counter
BEGIN
    DBMS_OUTPUT.PUT_LINE('Starting WHILE loop...');
    WHILE v_counter <= 5 LOOP -- Loop continues as long as v_counter is less than or equal to 5
        DBMS_OUTPUT.PUT_LINE('Iteration: ' || v_counter);
        v_counter := v_counter + 1; -- Increment the counter
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('WHILE loop finished.');
END;
/

Explanation

This example demonstrates a basic WHILE loop. The loop's execution is controlled by the condition v_counter <= 5. The DBMS_OUTPUT.PUT_LINE statement and v_counter increment only execute if the condition is true. Once v_counter becomes 6, the condition v_counter <= 5 evaluates to FALSE, and the loop terminates.

Example 2: WHILE Loop with User Input (Simulated)

DECLARE
    v_user_input VARCHAR2(10) := 'yes'; -- Simulate initial user input
    v_continue BOOLEAN := TRUE;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Simulating user interaction:');
    WHILE v_continue LOOP
        DBMS_OUTPUT.PUT_LINE('Do you want to continue? (yes/no)');
        -- In a real application, you would get input here, e.g., from a user interface or another source.
        -- For this example, we'll manually change v_user_input for demonstration.
        IF v_user_input = 'yes' THEN
            DBMS_OUTPUT.PUT_LINE('Continuing...');
            -- Simulate some processing
            -- For the next iteration, let's change input to 'no' to stop
            v_user_input := 'no'; -- This would be dynamic in a real scenario
        ELSIF v_user_input = 'no' THEN
            v_continue := FALSE; -- Set flag to FALSE to exit loop
            DBMS_OUTPUT.PUT_LINE('Stopping as requested.');
        ELSE
            v_continue := FALSE; -- Set flag to FALSE for invalid input
            DBMS_OUTPUT.PUT_LINE('Invalid input. Stopping.');
        END IF;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('WHILE loop based on user input finished.');
END;
/

Explanation

This example simulates a WHILE loop that continues based on user input. The WHILE v_continue condition controls the loop. Inside the loop, the v_user_input is checked. If it's 'yes', the loop continues. If 'no' or invalid, the v_continue flag is set to FALSE, causing the loop to terminate in the next iteration. This highlights how WHILE loops are effective when the termination condition is dynamic and changes based on internal logic or external factors.

Example 3: WHILE Loop for Factorial Calculation

DECLARE
    v_number NUMBER := 5; -- Number for which to calculate factorial
    v_factorial NUMBER := 1; -- Initialize factorial result
    v_temp_num NUMBER;
BEGIN
    IF v_number < 0 THEN
        DBMS_OUTPUT.PUT_LINE('Factorial is not defined for negative numbers.');
        RETURN; -- Exit the block
    END IF;

    v_temp_num := v_number; -- Use a temporary variable for the loop

    WHILE v_temp_num > 0 LOOP
        v_factorial := v_factorial * v_temp_num; -- Multiply factorial by current number
        v_temp_num := v_temp_num - 1; -- Decrement number
    END LOOP;

    DBMS_OUTPUT.PUT_LINE('Factorial of ' || v_number || ' is: ' || v_factorial);
END;
/

Explanation

This example demonstrates using a WHILE loop to calculate the factorial of a number. The loop continues as long as v_temp_num is greater than 0. In each iteration, v_factorial is updated, and v_temp_num is decremented. This is a classic example of using a WHILE loop where the number of iterations is determined by the initial value and the condition.

Example 4: WHILE Loop with EXIT (similar to basic loop, but emphasizes initial condition)

DECLARE
    v_product_price NUMBER := 10;
    v_total_cost NUMBER := 0;
    v_quantity NUMBER := 0;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Calculating total cost with WHILE loop:');
    WHILE v_total_cost < 50 LOOP -- Loop as long as total cost is less than 50
        v_quantity := v_quantity + 1;
        v_total_cost := v_total_cost + v_product_price;
        DBMS_OUTPUT.PUT_LINE('Quantity: ' || v_quantity || ', Current Total: $' || v_total_cost);
        -- An internal EXIT can still be used, though less common in pure WHILE loops
        IF v_quantity >= 10 THEN
            DBMS_OUTPUT.PUT_LINE('Maximum quantity reached, exiting.');
            EXIT; -- Force exit even if v_total_cost < 50
        END IF;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Final Total Cost: $' || v_total_cost || ', Final Quantity: ' || v_quantity);
END;
/

Explanation

While WHILE loops primarily rely on their initial condition, this example shows that an EXIT statement can still be used within a WHILE loop. Here, the loop normally runs as long as v_total_cost is less than 50. However, an additional EXIT condition is introduced: if v_quantity reaches 10, the loop terminates prematurely, demonstrating the flexibility of combining WHILE with EXIT.

Example 5: Infinite WHILE Loop (and how to avoid/handle)

DECLARE
    v_loop_control BOOLEAN := TRUE;
    v_count NUMBER := 0;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Demonstrating (and stopping) a potential infinite WHILE loop:');
    WHILE v_loop_control LOOP -- Condition is always TRUE initially
        v_count := v_count + 1;
        DBMS_OUTPUT.PUT_LINE('Looping... Count: ' || v_count);
        IF v_count >= 3 THEN
            DBMS_OUTPUT.PUT_LINE('Setting control to FALSE to exit.');
            v_loop_control := FALSE; -- Essential to change the condition to FALSE
        END IF;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('WHILE loop successfully terminated.');
END;
/

Explanation

This example demonstrates a common pitfall: an "infinite" WHILE loop if the loop's controlling condition never becomes false. Initially, v_loop_control is TRUE, making the WHILE condition always true. To prevent an actual infinite loop, an IF statement inside the loop explicitly sets v_loop_control to FALSE after v_count reaches 3, ensuring termination. This highlights the importance of ensuring that the WHILE condition will eventually evaluate to FALSE.

 

FOR Loop (including reverse)

The FOR loop is specifically designed for iterating over a predefined range of integers. It is particularly useful when you know in advance how many times you need to execute a block of code. The FOR loop automatically manages the loop counter, incrementing it with each iteration. It also supports iterating in reverse order.

Note: Oracle PL/SQL FOR loop, integer loop, fixed iterations, REVERSE FOR loop, loop counter, iteration range

Example 1: Basic FOR Loop

BEGIN
    DBMS_OUTPUT.PUT_LINE('Counting up with FOR loop:');
    FOR i IN 1..5 LOOP -- Loop from 1 to 5 (inclusive)
        DBMS_OUTPUT.PUT_LINE('Current value of i: ' || i);
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('FOR loop finished.');
END;
/

Explanation

This example demonstrates a basic FOR loop. The loop variable i is implicitly declared as a NUMBER within the loop's scope. It automatically iterates from 1 up to 5, incrementing by 1 in each step. The loop terminates once i exceeds 5. This is the simplest and most common use of a FOR loop when the number of iterations is fixed.

Example 2: FOR Loop with REVERSE

BEGIN
    DBMS_OUTPUT.PUT_LINE('Counting down with REVERSE FOR loop:');
    FOR j IN REVERSE 1..5 LOOP -- Loop from 5 down to 1
        DBMS_OUTPUT.PUT_LINE('Current value of j: ' || j);
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('REVERSE FOR loop finished.');
END;
/

Explanation

This example showcases the REVERSE keyword with a FOR loop. Instead of incrementing, the loop variable j now decrements from the upper bound (5) down to the lower bound (1). This is very convenient when you need to process items in reverse order without manually managing the decrementing counter.

Example 3: FOR Loop Iterating Over a Cursor (Implicit Cursor)

DECLARE
    -- No explicit cursor declaration needed for implicit FOR loop cursor
BEGIN
    DBMS_OUTPUT.PUT_LINE('Fetching employee names using implicit FOR loop cursor:');
    -- The FOR loop implicitly declares a record variable for each row fetched
    FOR emp_rec IN (SELECT first_name, last_name FROM employees WHERE ROWNUM <= 3 ORDER BY employee_id) LOOP
        DBMS_OUTPUT.PUT_LINE('Employee: ' || emp_rec.first_name || ' ' || emp_rec.last_name);
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Implicit FOR loop cursor finished.');
END;
/

Explanation

This is a highly recommended and common way to process data fetched from a database query. The FOR emp_rec IN (SELECT ...) syntax uses an implicit cursor. Oracle automatically opens the cursor, fetches each row into the emp_rec record (which is implicitly declared with the rowtype of the query), and then closes the cursor when all rows are processed or the loop exits. This greatly simplifies cursor management compared to explicit OPEN, FETCH, and CLOSE statements.

Example 4: FOR Loop with Nested Loops

BEGIN
    DBMS_OUTPUT.PUT_LINE('Demonstrating nested FOR loops:');
    FOR i IN 1..3 LOOP -- Outer loop
        DBMS_OUTPUT.PUT_LINE('Outer Iteration (i): ' || i);
        FOR j IN 1..2 LOOP -- Inner loop
            DBMS_OUTPUT.PUT_LINE('  Inner Iteration (j): ' || j);
        END LOOP;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Nested FOR loops finished.');
END;
/

Explanation

This example shows how FOR loops can be nested. The inner loop completes all its iterations for each single iteration of the outer loop. This structure is common for tasks like generating combinations, iterating over matrices, or any scenario where you need to perform operations for every pair of elements from two sets.

Example 5: FOR Loop with CONTINUE and EXIT (less common, but possible)

BEGIN
    DBMS_OUTPUT.PUT_LINE('FOR loop with CONTINUE and EXIT:');
    FOR k IN 1..10 LOOP
        IF k = 3 THEN
            DBMS_OUTPUT.PUT_LINE('  Skipping number ' || k);
            CONTINUE; -- Skip the rest of this iteration for k=3
        END IF;

        DBMS_OUTPUT.PUT_LINE('  Processing number ' || k);

        IF k = 7 THEN
            DBMS_OUTPUT.PUT_LINE('  Reached 7, exiting loop.');
            EXIT; -- Exit the loop entirely when k=7
        END IF;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('FOR loop with CONTINUE and EXIT finished.');
END;
/

Explanation

While FOR loops are typically meant for fixed iterations, CONTINUE and EXIT can still be used. CONTINUE will skip the remaining statements in the current iteration and proceed to the next value of the loop counter (e.g., skips printing for k=3). EXIT will terminate the loop entirely, regardless of the remaining range (e.g., stops the loop when k reaches 7). While valid, overusing EXIT in a FOR loop can sometimes make the code less readable as it deviates from the loop's defined iteration range.

 

GOTO Statement (and when to avoid it)

The GOTO statement in Oracle PL/SQL allows for unconditional branching to a specific labeled statement within the same block or subprogram. While it provides a way to jump to different parts of your code, its use is generally discouraged in modern programming practices due to its potential to create "spaghetti code" that is difficult to read, debug, and maintain. Structured control flow statements like IF-THEN-ELSE, CASE, and various loops are almost always preferred.

Note: Oracle PL/SQL GOTO, unconditional jump, code readability, spaghetti code, best practices, structured programming, when to avoid GOTO

Example 1: Basic GOTO to a Label

DECLARE
    v_counter NUMBER := 0;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Starting GOTO demonstration:');
    <<start_point>> -- Define a label
    v_counter := v_counter + 1;
    DBMS_OUTPUT.PUT_LINE('Current counter: ' || v_counter);

    IF v_counter < 3 THEN
        GOTO start_point; -- Jump back to the label
    END IF;

    DBMS_OUTPUT.PUT_LINE('GOTO demonstration finished.');
END;
/

Explanation

This example demonstrates a basic GOTO statement. The <<start_point>> defines a label. The GOTO start_point; statement causes the program execution to immediately jump back to the line immediately following the start_point label. This effectively creates a loop-like behavior, but it's less structured and harder to follow than a dedicated loop.

Example 2: GOTO for Error Handling (Less Recommended)

DECLARE
    v_value NUMBER := -5;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Checking value:');
    IF v_value < 0 THEN
        DBMS_OUTPUT.PUT_LINE('Error: Value cannot be negative.');
        GOTO error_handler; -- Jump to error handling section
    END IF;

    DBMS_OUTPUT.PUT_LINE('Processing value: ' || v_value);
    -- More processing logic here

    GOTO end_of_program; -- Jump to the end after successful processing

    <<error_handler>> -- Label for error handling
    DBMS_OUTPUT.PUT_LINE('Performing error cleanup and logging...');
    -- Real error handling logic (e.g., log, rollback)

    <<end_of_program>> -- Label for program exit
    DBMS_OUTPUT.PUT_LINE('Program finished.');
END;
/

Explanation

This example shows a potential (though generally discouraged) use of GOTO for error handling. If v_value is negative, execution jumps directly to the <<error_handler>> label, bypassing normal processing. After successful processing, it jumps to <<end_of_program>>. While this might seem convenient, PL/SQL's EXCEPTION handling block is the correct and structured way to manage errors.

Example 3: GOTO to Exit Nested Loops (Better alternatives exist)

DECLARE
    found_match BOOLEAN := FALSE;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Searching for match with GOTO:');
    FOR i IN 1..3 LOOP
        FOR j IN 1..3 LOOP
            DBMS_OUTPUT.PUT_LINE('Checking (' || i || ',' || j || ')');
            IF i = 2 AND j = 2 THEN
                DBMS_OUTPUT.PUT_LINE('Match found at (' || i || ',' || j || ')! Exiting loops with GOTO.');
                found_match := TRUE;
                GOTO end_of_search; -- Jump out of both loops
            END IF;
        END LOOP;
    END LOOP;

    <<end_of_search>> -- Label to jump to
    IF found_match THEN
        DBMS_OUTPUT.PUT_LINE('Search completed due to match.');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Search completed, no match found.');
    END IF;
END;
/

Explanation

This example uses GOTO to exit multiple nested loops prematurely once a condition is met. While EXIT can only exit the immediate loop, GOTO can jump completely out of any number of nested loops to a specified label. However, using a flag variable and checking it in outer loops (as demonstrated in Basic Loop Example 3 with labels) or refactoring into a procedure/function with RETURN is a more structured and readable approach for this scenario.

Example 4: GOTO to Skip Code Block (Generally Bad Practice)

DECLARE
    v_process_flag BOOLEAN := FALSE; -- Set to TRUE to process the block
BEGIN
    DBMS_OUTPUT.PUT_LINE('Demonstrating GOTO for skipping:');
    IF NOT v_process_flag THEN
        GOTO skip_block; -- Unconditionally skip to the label
    END IF;

    DBMS_OUTPUT.PUT_LINE('This block will be processed.');
    -- Extensive processing logic here
    DBMS_OUTPUT.PUT_LINE('Block processed.');

    <<skip_block>> -- Label to skip to
    DBMS_OUTPUT.PUT_LINE('Finished GOTO skip demonstration.');
END;
/

Explanation

This example illustrates using GOTO to skip a block of code based on a condition. If v_process_flag is false, the GOTO statement immediately jumps past the "processed" block. This is almost always better achieved with an IF-THEN statement where the "skipped" code is simply placed inside the THEN block (or ELSE block) that is only executed when the condition is met. GOTO in this context makes the flow non-linear and harder to reason about.

Example 5: GOTO Crossing Block Boundaries (Invalid)

-- This example demonstrates an INVALID GOTO usage. It will result in a compilation error.
-- You cannot GOTO into or out of a nested block, function, or procedure.

/*
DECLARE
    v_data NUMBER := 10;
BEGIN
    <<outer_label>>
    DBMS_OUTPUT.PUT_LINE('In outer block.');

    DECLARE -- Start of inner block
        v_inner_data NUMBER := 20;
    BEGIN
        DBMS_OUTPUT.PUT_LINE('In inner block.');
        -- INVALID: Cannot GOTO to a label in an enclosing block
        -- GOTO outer_label;
    END; -- End of inner block

    -- INVALID: Cannot GOTO to a label inside a nested block
    -- GOTO inner_label_here; -- (if inner_label_here existed inside the inner block)

    DBMS_OUTPUT.PUT_LINE('Back in outer block.');
END;
/
*/
-- To make this a valid, compilable example (but still illustrates the point about avoidance):
-- We will show an example of what GOTO is allowed to do,
-- and then comment on the limitations.

DECLARE
    v_count NUMBER := 0;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Demonstrating GOTO within the same block:');
    <<loop_start>>
    v_count := v_count + 1;
    DBMS_OUTPUT.PUT_LINE('Count: ' || v_count);
    IF v_count < 3 THEN
        GOTO loop_start;
    END IF;
    DBMS_OUTPUT.PUT_LINE('Finished GOTO within same block.');
    -- It is IMPORTANT to remember that GOTO cannot jump into or out of compound statements
    -- like IF statements, loops, or exception handlers, or different subprograms.
    -- It can only jump within the same declarative or executable section of a single block.
END;
/

Explanation

This example (initially commented out to prevent compilation error) is crucial because it highlights a major restriction of GOTO: it cannot transfer control into or out of different PL/SQL blocks, subprograms (functions, procedures), IF statements, or loops. It can only jump to a label within the same executable section of the current block. The working part of the example simply reiterates Example 1 to show valid usage while the comments emphasize the strong limitations, which further underscore why GOTO is rarely the right tool. Structured control flow mechanisms are designed precisely to handle these block transitions cleanly and safely.