Oracle PL/SQL

Complete PL/SQL Exercises: From Beginner to Advanced

complete plsql
Written by shohal

The goal of this post is to help students, job seekers, and professionals practice PL/SQL coding in a structured way and master problem-solving skills for exams, interviews, and real-world database projects.

Exercise 1 – Hello PL/SQL

Write a block that prints ‘Hello, PL/SQL!’ using DBMS_OUTPUT.

Exercise 2 – Simple Addition

Declare two variables a and b, assign values, and print their sum.

Exercise 3 – IF Statement

Check if a number is positive or negative and print result.

Exercise 4 – Multiplication Table

Print the multiplication table of 5 using a LOOP.

Exercise 5 – Factorial

Write a PL/SQL program to calculate factorial of 5.

Exercise 6 – Prime Check

Check if a given number is prime.

Exercise 7 – Procedures

Write a procedure that accepts a number and prints whether it is odd or even.

— Create the procedure

— Call the procedure

Exercise 8 – Functions

Write a function that returns the square of a number.

Exercise 9 – Cursor Example

Use an explicit cursor to fetch and print employee names.

Exercise 10 – Exception Handling

Write code that divides two numbers and handles divide-by-zero.

Exercise 11 – User-defined Exception

Raise and handle a custom exception when salary < 1000.

Exercise 12 – Nested Blocks

Create a block inside another, handling exceptions separately.

Exercise 13 – Packages

Create a package with a procedure and function for arithmetic operations.

————Package Specification———————-

—————-Package Body—————

——————Test the Package———————-

Exercise 14 – Overloading

Overload a function to calculate area of circle and rectangle.

—————-Create a Package Specification———————

———————Package Body————————

—————–Test the Overloaded Functions——————–

Exercise 15 – Triggers

Create a trigger that logs inserts on the employee table.

————–Create a Log Table ———————–

——————Create the Trigger——————

—————-Test the Trigger—————–

— Insert a new employee

— Check the log table

Exercise 16 – Bulk Collect

Fetch multiple rows into a collection using BULK COLLECT.

Exercise 17 – FORALL

Use FORALL to insert multiple records into a table efficiently.

Exercise 18 – Record Types

Create and use a RECORD type for student info.

Exercise 19 – Object Types

Define an object type ‘Car’ with attributes and methods.

Exercise 20 – Nested Tables

Store multiple phone numbers for an employee in a nested table.

— Create a collection type for phone numbers

— Normal query (phones not expanded)

— Expand phone numbers using TABLE()

Exercise 21 – VARRAY

— Create a VARRAY type that can hold max 5 scores

Exercise 22 – Autonomous Transactions

Write a procedure that logs audit records using PRAGMA AUTONOMOUS_TRANSACTION.

Exercise 23 – Materialized Views with PL/SQL

Refresh and query a materialized view inside PL/SQL.

— Insert some sample data

Exercise 24 – DBMS_SCHEDULER

Schedule a PL/SQL job that runs daily at midnight.

Exercise 25 – Dynamic SQL

Execute a dynamically built query using EXECUTE IMMEDIATE.

  1. Simple Dynamic SELECT INTO Variable

  • Dynamic DML (INSERT/UPDATE/DELETE)

  • Dynamic Query with Parameters (Using Bind Variables)

  • Returning Multiple Rows with BULK COLLECT

Exercise 26 – Bind Variables

Use bind variables with dynamic SQL for better performance.

1. Dynamic SELECT with a Single Bind

2. Dynamic SELECT with Multiple Binds

3. Dynamic DML with Binds

4. Fetch Multiple Rows with Binds (BULK COLLECT)

Exercise 27 – Native Compilation

Compile a PL/SQL unit natively for performance boost.

  1. One-off: compile a specific unit as native

— Procedure / Function / Package / Trigger

  • Session/system default (affects new compilations)

— Session scope (your current connection)

ALTER SESSION SET plsql_code_type      = NATIVE;

ALTER SESSION SET plsql_optimize_level = 3;

— Instance/system scope (needs privileges; affects future compilations)

ALTER SYSTEM SET plsql_code_type      = NATIVE  SCOPE=BOTH;

ALTER SYSTEM SET plsql_optimize_level = 3       SCOPE=BOTH;

  • Force recompilation so existing units pick up the setting

— Recompile one schema’s invalids

BEGIN

  UTL_RECOMP.recomp_serial(schema => USER);   — or a specific schema name

END;

/

— Or recompile a specific object after changing the session setting

ALTER PROCEDURE my_proc COMPILE;  — will use current session’s plsql_code_type

  • Verify what actually happened

— Check per-object settings (your schema)

SELECT name, type, plsql_code_type, plsql_optimize_level

FROM   user_plsql_object_settings

ORDER  BY name;

— Check dictionary view that shows what’s in effect

SELECT object_name, object_type, plsql_code_type, plsql_optimize_level

FROM   user_plsql_object_settings

WHERE  name = ‘MY_PROC’;

  • Measure quickly

SET SERVEROUTPUT ON

DECLARE

  t1 TIMESTAMP; t2 TIMESTAMP;

BEGIN

  t1 := SYSTIMESTAMP;

  — call hot code many times

  FOR i IN 1..100000 LOOP

    my_proc; — or my_func(…)

  END LOOP;

  t2 := SYSTIMESTAMP;

  DBMS_OUTPUT.PUT_LINE(‘Elapsed: ‘ || TO_CHAR(t2 – t1));

END;

/

  • Notes & gotchas

·  Modern Oracle (11gR2+): native compilation is integrated—no external C compiler or native library directories required.

·  Native helps most when code is CPU-bound with tight loops and minimal SQL context switching. If your time is in SQL, optimize SQL first.

·  Keep PLSQL_WARNINGS on during development:

ALTER SESSION SET plsql_warnings = ‘ENABLE:ALL’;

·  If you toggle plsql_code_type at the session level, you must recompile units in that session to change them to native/interpreted.

Exercise 28 – Distributed Transactions

Write a block that updates two remote databases in a single transaction.

DECLARE

    v_errmsg VARCHAR2(4000);

BEGIN

    — Update remote database #1

    UPDATE employees@db_link1

       SET salary = salary * 1.10

     WHERE department_id = 50;

    — Update remote database #2

    UPDATE employees@db_link2

       SET salary = salary * 1.05

     WHERE department_id = 60;

    — If all updates succeed, commit both together

    COMMIT;

EXCEPTION

    WHEN OTHERS THEN

        — Rollback all updates if any error occurs

        ROLLBACK;

        v_errmsg := SQLERRM;

        DBMS_OUTPUT.PUT_LINE(‘Error occurred: ‘ || v_errmsg);

END;

/

Exercise 29 – Parallel Execution

Invoke DBMS_PARALLEL_EXECUTE to process large data in chunks.

Suppose we have a table:

CREATE TABLE big_sales (

    sale_id     NUMBER PRIMARY KEY,

    amount      NUMBER,

    processed   CHAR(1) DEFAULT ‘N’

);

We want to update millions of rows, setting processed = 'Y' — but in parallel chunks for performance.

1.Create a Task

BEGIN

    DBMS_PARALLEL_EXECUTE.CREATE_TASK(‘process_sales_task’);

END;

/

2.Divide Table into Chunks by Rowid

BEGIN

    DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_ROWID(

        task_name   => ‘process_sales_task’,

        table_owner => ‘HR’,

        table_name  => ‘BIG_SALES’,

        by_row      => TRUE,

        chunk_size  => 10000  — rows per chunk

    );

END;

/

3. Define SQL to Run on Each Chunk

BEGIN

    DBMS_PARALLEL_EXECUTE.RUN_TASK(

        task_name      => ‘process_sales_task’,

        sql_stmt       => ‘UPDATE big_sales

                           SET processed = ”Y”

                           WHERE ROWID BETWEEN :start_id AND :end_id’,

        language_flag  => DBMS_SQL.NATIVE,

        parallel_level => 4  — run 4 workers in parallel

    );

END;

/

4.Monitor Task Progress

SELECT task_name, status, start_time, end_time

FROM   user_parallel_execute_tasks

WHERE  task_name = ‘process_sales_task’;

5.Drop the Task After Completion

BEGIN

    DBMS_PARALLEL_EXECUTE.DROP_TASK(‘process_sales_task’);

END;

/

     Exercise 30 – Advanced Optimizations

Profile PL/SQL code using DBMS_PROFILER and optimize it.

🔹 Step 1: Setup for Profiling

— Create profiler tables (one-time setup)

@$ORACLE_HOME/rdbms/admin/profload.sql

🔹 Step 2: Example PL/SQL Code (Before Optimization)

CREATE OR REPLACE PROCEDURE slow_sum IS

    v_total NUMBER := 0;

BEGIN

    FOR i IN 1..100000 LOOP

        v_total := v_total + i;

    END LOOP;

    DBMS_OUTPUT.PUT_LINE(‘Total: ‘ || v_total);

END;

/

🔹 Step 3: Profile the Code

— Start profiling

EXEC DBMS_PROFILER.START_PROFILER(‘Profiling slow_sum procedure’);

— Run the procedure

EXEC slow_sum;

— Stop profiling

EXEC DBMS_PROFILER.STOP_PROFILER;

🔹 Step 4: Analyze Profile Data

— Find runs

SELECT runid, run_comment, run_date

FROM   plsql_profiler_runs

ORDER  BY runid DESC;

— Check time spent in each unit

SELECT u.unit_name, d.total_occur, d.total_time

FROM   plsql_profiler_units u

JOIN   plsql_profiler_data d ON u.unit_number = d.unit_number

WHERE  u.unit_name = ‘SLOW_SUM’;

🔹 Step 5: Optimize the Code

CREATE OR REPLACE PROCEDURE fast_sum IS

    v_total NUMBER := 0;

    v_n     NUMBER := 100000;

BEGIN

    — Formula for sum of first N natural numbers = n(n+1)/2

    v_total := v_n * (v_n + 1) / 2;

    DBMS_OUTPUT.PUT_LINE(‘Total: ‘ || v_total);

END;

/

🔹 Step 6: Re-Profile the Optimized Code

EXEC DBMS_PROFILER.START_PROFILER(‘Profiling fast_sum procedure’);

EXEC fast_sum;

EXEC DBMS_PROFILER.STOP_PROFILER;

Exercise 31 – Polymorphic Table Functions (PTFs)

Goal: Create functions that adapt to input table structures.

CREATE OR REPLACE PACKAGE ptf_pkg AS

    FUNCTION describe(tab IN OUT DBMS_TF.table_t) RETURN DBMS_TF.describe_t;

    PROCEDURE fetch_rows;

END ptf_pkg;

/

— Example is simplified — PTFs are advanced features for dynamic query shaping

Try:

Implement a PTF that masks sensitive columns dynamically.

Exercise 32 – Secure Coding with Code Signing

Goal: Sign PL/SQL code for tamper detection.

— Create wallet and certificate outside PL/SQL

— Example command (done in OS):

— orapki wallet create

— In PL/SQL:

ALTER PROCEDURE sensitive_proc SIGN;

Try:

  • Test signature verification after altering code.

Exercise 33 – Application Context for Multi-Tenant Security

Goal: Dynamically filter data per tenant/user.

BEGIN

    DBMS_SESSION.SET_CONTEXT(‘app_ctx’, ‘tenant_id’, ‘T1001’);

END;

/

CREATE OR REPLACE FUNCTION tenant_policy_fn (

    schema_name VARCHAR2,

    table_name  VARCHAR2

) RETURN VARCHAR2 IS

BEGIN

    RETURN ‘tenant_id = SYS_CONTEXT(”app_ctx”, ”tenant_id”)’;

END;

      Try:

  • Switch tenant context and see filtered results.

Exercise 34 – Real-Time Streaming via Advanced Queuing

Goal: Use AQ to send messages between sessions.

BEGIN

    DBMS_AQADM.CREATE_QUEUE_TABLE(

        queue_table => ‘message_queue_table’,

        queue_payload_type => ‘SYS.AQ$_JMS_TEXT_MESSAGE’

    );

    DBMS_AQADM.CREATE_QUEUE(

        queue_name => ‘message_queue’,

        queue_table => ‘message_queue_table’

    );

    DBMS_AQADM.START_QUEUE(queue_name => ‘message_queue’);

END;

Try:

  • Publish and consume messages in different sessions.

Exercise 35 – Sharded Database PL/SQL

Goal: Execute PL/SQL in a sharded environment.

— Requires Oracle Sharding setup

— Example: Run on specific shard

EXECUTE IMMEDIATE ‘ALTER SESSION SET CONTAINER = shard1’;

Try:

  • Implement cross-shard data aggregation procedure.

Exercise 36 – PL/SQL Code Generation

Goal: Write PL/SQL that writes PL/SQL.

DECLARE

    v_code VARCHAR2(4000);

BEGIN

    v_code := ‘BEGIN DBMS_OUTPUT.PUT_LINE(”Generated Code Execution”); END;’;

    EXECUTE IMMEDIATE v_code;

END;

      Try:

  • Generate a procedure dynamically from metadata tables.

About the author

shohal

1 Comment

  • I must say this article is extremely well written, insightful, and packed with valuable knowledge that shows the author’s deep expertise on the subject, and I truly appreciate the time and effort that has gone into creating such high-quality content because it is not only helpful but also inspiring for readers like me who are always looking for trustworthy resources online. Keep up the good work and write more. i am a follower.

Leave a Comment