PL/SQL

📋 Exam scope
PL/SQL appears ONLY in the FA exam — 5 MCQs. Not in PRA or sprints. If FA is near, study this. Otherwise bookmark it.

1. What is PL/SQL?

Imagine you're at a restaurant. SQL is like pointing at a menu item and saying "give me that" — you can ask for data, but that's it. You can't say "if the pasta is sold out, get me pizza instead" or "keep bringing bread until I say stop." SQL has no logic — no decisions, no loops, no error handling.

PL/SQL (Procedural Language / SQL) is Oracle's way of adding a brain to SQL. It wraps programming logic — IF/ELSE decisions, loops, variables, error handling — around your SQL statements so they can run as a complete program inside the database itself.

✅ Key fact
PL/SQL = SQL + procedural programming. It is Oracle-specific (not MySQL, not PostgreSQL). Think of it as writing a small program that lives and runs inside the database — no external application needed.

Why does it matter? Without PL/SQL, your application would need to send dozens of separate SQL commands over the network. With PL/SQL, you bundle all that logic into one block, send it once, and the database handles it. Faster, cleaner, fewer network round trips.

🔍 Real-world analogy
SQL alone is like texting a friend one instruction at a time: "Open the fridge." "Is there milk?" "If yes, pour a glass." "If no, go to the store." Each message is a separate trip.

PL/SQL is like handing them a written note with ALL the instructions at once. They read the whole thing and do it all in one go. That's the efficiency gain.
Question

What does PL/SQL add to regular SQL?

  1. The ability to create tables
  2. Procedural logic like IF/ELSE, loops, and error handling
  3. Support for NoSQL databases
  4. A graphical user interface for queries
B) SQL can already create tables. PL/SQL's purpose is adding programming constructs — decisions, loops, variables, exception handling — on top of SQL. It turns SQL from a "question-answer" tool into a full programming language inside the database.
Question

PL/SQL is specific to which database system?

  1. MySQL
  2. PostgreSQL
  3. Oracle
  4. SQL Server
C) PL/SQL is Oracle's proprietary procedural extension. MySQL uses stored procedures with a different syntax, PostgreSQL has PL/pgSQL, and SQL Server has T-SQL. If the exam asks about PL/SQL, it's always Oracle.

2. Block Structure — The #1 Exam Topic

Every PL/SQL program is organized into blocks. Think of a block like a recipe card:

-- PL/SQL block structure
DECLARE
  -- Variables (OPTIONAL) — "ingredients list"
  v_name VARCHAR2(50);
BEGIN
  -- Your logic (MANDATORY) — "cooking steps"
  v_name := 'Darshan';
  DBMS_OUTPUT.PUT_LINE('Hello, ' || v_name);
EXCEPTION
  -- Error handling (OPTIONAL) — "backup plan"
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Error occurred');
END;
/
⚠️ Common trap
The block ends with END; (semicolon) followed by / on the next line. The / tells Oracle to execute the block. Forgetting the / means your block just sits there doing nothing.
💡 Exam tip
"Which section is mandatory?" → BEGIN. DECLARE and EXCEPTION are optional. The simplest valid block is just BEGIN ... END;
✅ Section order — memorize
DECLARE (optional) → BEGIN (mandatory) → EXCEPTION (optional) → END; (mandatory)
Memory trick: D-B-E-E — "Don't Bother Except Errors"

Anonymous blocks vs Named blocks: The block above is anonymous — it runs once and disappears. When you save a block as a procedure, function, or trigger, it becomes a named block stored in the database. Same structure, just reusable.

Question

Which section of a PL/SQL block is MANDATORY?

  1. DECLARE
  2. BEGIN
  3. EXCEPTION
  4. Both DECLARE and EXCEPTION
B) BEGIN is the only mandatory section. You can write a valid block with just BEGIN and END — no DECLARE, no EXCEPTION. The simplest block: BEGIN NULL; END;
Question

Correct order of PL/SQL block sections?

  1. BEGIN → DECLARE → EXCEPTION → END
  2. DECLARE → BEGIN → EXCEPTION → END
  3. DECLARE → EXCEPTION → BEGIN → END
  4. BEGIN → EXCEPTION → DECLARE → END
B) DECLARE → BEGIN → EXCEPTION → END. Always. You can't handle exceptions before you've done the work, and you can't do work before declaring your tools. Think recipe: ingredients first, steps second, backup plan last.

3. Variables & Data Types

Why do we need variables? Imagine you're a cashier. A customer buys 3 items. You need a temporary place to hold the running total while you scan each one. That's a variable — a named container that holds a value while your program runs.

In PL/SQL, variables live in the DECLARE section. You give each one a name, a type (what kind of data it holds), and optionally an initial value.

DECLARE
  v_salary   NUMBER(10, 2);          -- number with 2 decimals
  v_name     VARCHAR2(100);          -- variable-length text (up to 100 chars)
  v_dob      DATE;                   -- date + time
  v_active   BOOLEAN;               -- TRUE / FALSE / NULL
BEGIN
  v_salary := 75000;  -- Assignment uses := (not =)
END;
/
⚠️ := not =
PL/SQL uses := for assignment (put a value into a variable). The single = is only used for comparison. This trips up every beginner. If you write v_salary = 75000, Oracle will throw an error.

Anchor Types — The Smart Way

The problem: You declare v_name VARCHAR2(50), but the actual column in the employees table is VARCHAR2(100). Now your variable is too small and your program crashes. Worse — if someone changes the column size later, your code silently breaks.

The solution: Instead of hardcoding the type, anchor your variable to the column itself. "Whatever type that column is, make me the same."

DECLARE
  -- %TYPE — borrows type from ONE column
  v_emp_name employees.first_name%TYPE;     -- "same type as first_name column"

  -- %ROWTYPE — borrows structure of ENTIRE row
  v_emp_row  employees%ROWTYPE;            -- has .first_name, .salary, .dept_id, etc.

  -- CONSTANT — a value that NEVER changes
  c_tax_rate CONSTANT NUMBER := 0.18;     -- must assign value immediately
BEGIN
  NULL;
END;
/
✅ %TYPE vs %ROWTYPE — know the difference
%TYPE = copies a single column's data type. Use when you need one value.
%ROWTYPE = copies the entire row structure. Use when you need a whole record.

Think of it this way: %TYPE is like copying one column header from a spreadsheet. %ROWTYPE is like copying the entire header row.
Question

What does %TYPE do?

  1. Matches an entire table row
  2. Matches a specific column's data type
  3. Creates a record type
  4. Defines function return type
B) %TYPE anchors a variable to a single column's type. If the column is VARCHAR2(100), your variable automatically becomes VARCHAR2(100). %ROWTYPE is the one that matches a whole row.
Question

How is a CONSTANT declared in PL/SQL?

  1. CONST c_rate NUMBER := 0.18;
  2. c_rate CONSTANT NUMBER := 0.18;
  3. DECLARE CONSTANT c_rate = 0.18;
  4. FIXED c_rate NUMBER := 0.18;
B) The syntax is: name CONSTANT datatype := value; — the keyword CONSTANT goes between the name and the type. It must be initialized on the same line (you can't assign it later).

4. Control Structures

Why? Without control structures, your program runs top to bottom like a conveyor belt — every line executes, no matter what. But real problems need decisions ("is this employee eligible for a bonus?") and repetition ("process every row in this table"). Control structures give your program a brain.

IF Statement — Making Decisions

Works exactly like you'd expect: check a condition, do something if true, something else if false.

IF v_score >= 85 THEN
  DBMS_OUTPUT.PUT_LINE('Distinction');
ELSIF v_score >= 60 THEN    -- Note: ELSIF not ELSEIF
  DBMS_OUTPUT.PUT_LINE('First Class');
ELSE
  DBMS_OUTPUT.PUT_LINE('Pass');
END IF;
⚠️ Spelling trap
It's ELSIF — not ELSEIF, not ELSE IF. The exam will offer all three as options. Pick ELSIF every time.

CASE Statement — Cleaner Multi-Way Branching

When you have many specific values to check (like a grade or status code), CASE is cleaner than chaining IF/ELSIF.

CASE v_grade
  WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');
  WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Good');
  ELSE          DBMS_OUTPUT.PUT_LINE('Average');
END CASE;

Loops — Doing Things Repeatedly

PL/SQL gives you three types of loops. Think of them as three ways to eat a bag of chips:

-- 1. Basic LOOP — needs EXIT to stop (or it runs forever!)
LOOP
  DBMS_OUTPUT.PUT_LINE(v_i);
  v_i := v_i + 1;
  EXIT WHEN v_i > 5;
END LOOP;

-- 2. WHILE LOOP — checks condition FIRST, then executes
WHILE v_j <= 5 LOOP
  DBMS_OUTPUT.PUT_LINE(v_j);
  v_j := v_j + 1;
END LOOP;

-- 3. FOR LOOP — counter managed automatically, no manual increment
FOR i IN 1..5 LOOP
  DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
Loop typeWhen to useStops via
LOOPUnknown iterations, exit mid-loopEXIT / EXIT WHEN
WHILE LOOPCondition checked before each iterationCondition becomes FALSE
FOR LOOPKnown count (1 to N)Range exhausted automatically
Question

Which loop MUST have EXIT or EXIT WHEN to prevent infinite execution?

  1. FOR LOOP
  2. WHILE LOOP
  3. Basic LOOP...END LOOP
  4. Cursor FOR loop
C) The basic LOOP has no built-in stop condition. Without EXIT or EXIT WHEN, it loops forever. WHILE stops when its condition is false. FOR stops when the range is exhausted. Only the basic LOOP needs an explicit exit.
Question

What is the correct keyword for "else if" in PL/SQL?

  1. ELSE IF
  2. ELSEIF
  3. ELSIF
  4. ELIF
C) ELSIF — no space, no second E. This is one of the most commonly tested spelling traps. ELSE IF (two words) is wrong. ELSEIF (one word with E) is wrong. ELIF is Python, not PL/SQL.

5. Cursors

The problem: When you use SELECT INTO in PL/SQL, it expects exactly ONE row back. But what if your query returns 50 employees? SELECT INTO crashes with TOO_MANY_ROWS. You need a way to process rows one at a time.

That's what a cursor is. Think of it as a bookmark in a book. The database runs your query and gets back a stack of results. The cursor points to the current row — you read it, process it, then move the cursor to the next row. Repeat until done.

🔍 Analogy
Imagine a stack of student exam papers on your desk. You can't grade them all at once — you pick up the top one (FETCH), grade it (process), then pick the next. The cursor is your hand pointing at "which paper am I on right now?" When the stack is empty, you stop.
✅ Two types of cursors
Implicit cursor: Oracle creates it automatically behind the scenes for every INSERT, UPDATE, DELETE, and SELECT INTO. You don't declare it — Oracle handles everything. You can check what happened using SQL%FOUND, SQL%NOTFOUND, SQL%ROWCOUNT.

Explicit cursor: YOU declare it for multi-row SELECT queries. You control the whole lifecycle: OPEN → FETCH → CLOSE. Use this when a query returns multiple rows.

Implicit Cursor — Automatic

Every time you run a DML statement, Oracle silently creates an implicit cursor. You don't see it, but you can ask it questions:

BEGIN
  UPDATE employees SET salary = salary * 1.1 WHERE dept_id = 10;

  -- SQL%ROWCOUNT tells you how many rows were affected
  DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' employees got a raise');

  IF SQL%NOTFOUND THEN
    DBMS_OUTPUT.PUT_LINE('No employees in dept 10');
  END IF;
END;
/

Explicit Cursor — 4-Step Pattern

When your SELECT returns many rows, you declare an explicit cursor and follow this strict sequence:

  1. DECLARE — define the query the cursor will run
  2. OPEN — execute the query and create the result set
  3. FETCH — grab one row at a time (in a loop)
  4. CLOSE — release the resources
DECLARE
  -- Step 1: DECLARE the cursor (define the query)
  CURSOR c_emp IS
    SELECT first_name, salary FROM employees WHERE dept_id = 10;
  v_name employees.first_name%TYPE;
  v_sal  employees.salary%TYPE;
BEGIN
  OPEN c_emp;           -- Step 2: OPEN (runs the query)
  LOOP
    FETCH c_emp INTO v_name, v_sal;  -- Step 3: FETCH one row
    EXIT WHEN c_emp%NOTFOUND;       -- stop when no more rows
    DBMS_OUTPUT.PUT_LINE(v_name || ' earns ' || v_sal);
  END LOOP;
  CLOSE c_emp;          -- Step 4: CLOSE (free resources)
END;
/
⚠️ Always CLOSE your cursors
An unclosed cursor is like leaving a file open on your computer — it wastes memory. Oracle has a limit on open cursors. If you forget to close them, eventually you'll hit ORA-01000: maximum open cursors exceeded.

Cursor Attributes

AttributeReturnsMeaning
%FOUNDBOOLEANTRUE if last FETCH got a row
%NOTFOUNDBOOLEANTRUE if last FETCH found nothing (used to exit loops)
%ROWCOUNTNUMBERHow many rows fetched so far
%ISOPENBOOLEANTRUE if cursor is currently open

Cursor FOR Loop — The Shortcut

The 4-step pattern works, but it's verbose. PL/SQL offers a shortcut that handles OPEN, FETCH, and CLOSE automatically:

BEGIN
  FOR emp IN (SELECT first_name, salary FROM employees) LOOP
    DBMS_OUTPUT.PUT_LINE(emp.first_name || ' — ' || emp.salary);
  END LOOP;  -- auto OPEN, FETCH, CLOSE — no manual work
END;
/
💡 Exam tip
The cursor FOR loop is the preferred way in real code because it's shorter and Oracle handles cleanup. But the exam loves to test the 4-step pattern (DECLARE → OPEN → FETCH → CLOSE), so know both.
Question

Correct sequence for an explicit cursor?

  1. OPEN → DECLARE → FETCH → CLOSE
  2. DECLARE → FETCH → OPEN → CLOSE
  3. DECLARE → OPEN → FETCH → CLOSE
  4. OPEN → FETCH → DECLARE → CLOSE
C) DECLARE → OPEN → FETCH → CLOSE. You must define the query first (DECLARE), then execute it (OPEN), then read rows one by one (FETCH), and finally release resources (CLOSE). You can't FETCH from a cursor that hasn't been OPENed.
Question

Cursor attribute that returns TRUE when the last FETCH got a row?

  1. %NOTFOUND
  2. %ISOPEN
  3. %ROWCOUNT
  4. %FOUND
D) %FOUND returns TRUE when the last FETCH successfully retrieved a row. %NOTFOUND is the opposite — TRUE when there are no more rows. %ROWCOUNT returns a number, not a boolean. %ISOPEN checks if the cursor is open, not if data was found.

6. Procedures

The problem: You've written a PL/SQL block that gives an employee a raise. It works great. But now you need to use the same logic in 5 different places. Are you going to copy-paste the block 5 times? What happens when the logic changes — do you update all 5 copies?

A procedure is a named, reusable block of code stored in the database. Write it once, call it from anywhere. It's like saving a recipe in a cookbook — you don't rewrite it every time you cook; you just say "follow the raise recipe."

🔍 Analogy
A procedure is like a vending machine. You put in inputs (coins + selection), it does its job internally (retrieves the item), and optionally gives you something back (change through OUT parameter). The key insight: a procedure DOES something — it performs an action. It doesn't have to return a value.
CREATE OR REPLACE PROCEDURE give_raise (
  p_emp_id  IN  employees.employee_id%TYPE,  -- input: which employee
  p_amount  IN  NUMBER,                     -- input: raise amount
  p_new_sal OUT NUMBER                      -- output: new salary after raise
) IS
  v_old_sal NUMBER;
BEGIN
  SELECT salary INTO v_old_sal FROM employees WHERE employee_id = p_emp_id;
  p_new_sal := v_old_sal + p_amount;
  UPDATE employees SET salary = p_new_sal WHERE employee_id = p_emp_id;
END give_raise;
/
✅ Parameter modes — critical for exam
IN — read-only input. The procedure can read it but not change it. This is the default if you don't specify a mode.
OUT — write-only output. The procedure sets a value that the caller can read after the call.
IN OUT — both directions. The caller passes a value in, the procedure can modify it, and the caller sees the change.
💡 Naming convention
Notice the prefixes: p_ for parameters, v_ for local variables, c_ for constants. This isn't required by Oracle, but it's standard practice and helps you instantly know what something is.
Question

What is the default parameter mode in a PL/SQL procedure?

  1. OUT
  2. IN
  3. IN OUT
  4. RETURN
B) If you don't specify a mode, it defaults to IN (read-only). So p_name VARCHAR2 is the same as p_name IN VARCHAR2. The procedure can read the value but cannot modify it.

7. Functions

Functions are like procedures with one key difference: a function MUST return exactly one value.

Think of the difference this way: A procedure is like telling someone "go clean the room" — they do it, but they don't hand you anything back. A function is like asking "what's 2 + 2?" — they MUST give you an answer back.

CREATE OR REPLACE FUNCTION get_annual_salary (
  p_emp_id IN employees.employee_id%TYPE
) RETURN NUMBER IS
  v_monthly NUMBER;
BEGIN
  SELECT salary INTO v_monthly FROM employees WHERE employee_id = p_emp_id;
  RETURN v_monthly * 12;  -- MUST have a RETURN statement
END;
/
✅ Function vs Procedure — know this cold
FeatureProcedureFunction
Returns a value?No (uses OUT params optionally)Yes, MUST return exactly one
RETURN keyword?Not requiredRequired in header AND body
Can use in SQL SELECT?NoYes!
Primary purposePerform an action (update, insert, etc.)Calculate and return a value

The big advantage of functions: You can use them directly inside SQL queries. This is something procedures cannot do.

-- Using a function inside a SELECT — procedures CAN'T do this
SELECT first_name, get_annual_salary(employee_id) AS annual
FROM employees;
Question

Key difference between PROCEDURE and FUNCTION?

  1. Procedures can have parameters; functions cannot
  2. Functions must return a value; procedures need not
  3. Procedures are stored; functions are not
  4. Functions cannot call procedures
B) A function MUST return exactly one value using the RETURN keyword. A procedure performs an action but doesn't have to return anything (though it can pass values back through OUT parameters). Both can have parameters, both are stored in the database.
Question

Where CAN a function be used that a procedure CANNOT?

  1. Inside a PL/SQL block
  2. Inside a SQL SELECT statement
  3. Inside a trigger
  4. Inside another procedure
B) Functions can be called directly inside SQL statements (SELECT, WHERE, etc.) because they return a value that SQL can use. Procedures cannot appear in SQL — they must be called with EXECUTE or from within a PL/SQL block.

8. Triggers

Why do triggers exist? Sometimes you want something to happen automatically every time data changes — without relying on the application to remember. For example: "Every time someone inserts a new employee, automatically set the created_at timestamp." Or: "If someone tries to insert a negative salary, block it."

A trigger is a PL/SQL block that fires automatically when a specific event (INSERT, UPDATE, DELETE) happens on a table. You don't call a trigger — Oracle calls it for you. It's like a burglar alarm: you set it up once, and it activates on its own when someone opens the door.

🔍 Analogy
Think of a trigger as a security guard at a building entrance. A BEFORE trigger is a guard who checks your ID before you enter — they can stop you or modify your entry pass. An AFTER trigger is a CCTV camera that records your entry after you've already walked in — it can log what happened but can't change it.
CREATE OR REPLACE TRIGGER trg_before_insert
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
  -- Validation: reject bad data BEFORE it enters the table
  IF :NEW.salary < 0 THEN
    RAISE_APPLICATION_ERROR(-20001, 'Salary cannot be negative');
  END IF;
  -- Auto-fill: set timestamp automatically
  :NEW.created_at := SYSDATE;
END;
/

:NEW and :OLD — The Two Magic Variables

Inside a row-level trigger, you have access to the data being changed:

Operation:OLD:NEW
INSERTNULL (nothing existed before)Values being inserted
UPDATEValues before updateValues after update
DELETEValues being deletedNULL (nothing will exist after)
💡 Exam tip
In a DELETE trigger, :NEW is NULL (the row is being removed — there's no "new" version). In an INSERT trigger, :OLD is NULL (the row didn't exist before). Only UPDATE has BOTH :OLD and :NEW.
ConceptDetail
BEFORE triggerFires before DML executes. Can modify :NEW values. Best for validation and auto-filling fields.
AFTER triggerFires after DML completes. Cannot modify :NEW. Best for logging and auditing.
FOR EACH ROWMakes it a row-level trigger — fires once per affected row. Required to use :NEW/:OLD.
Statement-levelWithout FOR EACH ROW — fires once per statement regardless of how many rows are affected.
⚠️ BEFORE vs AFTER — key distinction
Only BEFORE triggers can modify :NEW values (because the data hasn't been written yet). AFTER triggers see the data after it's already committed — too late to change it. If the exam asks "which trigger can modify :NEW?" the answer is always BEFORE.
Question

In a BEFORE INSERT trigger, which holds the values being inserted?

  1. :OLD
  2. :NEW
  3. :INSERTED
  4. Both :OLD and :NEW
B) :NEW holds the incoming values for INSERT. :OLD is NULL because nothing existed before the insert — there's no "old" version of a row that's being created for the first time. :INSERTED doesn't exist in PL/SQL.
Question

Which trigger can modify :NEW values?

  1. AFTER trigger
  2. INSTEAD OF trigger
  3. BEFORE trigger
  4. AUTONOMOUS trigger
C) BEFORE triggers fire before the data is written to the table, so :NEW can still be changed. AFTER triggers fire after the write — :NEW is read-only at that point. Think: a guard at the door can change your entry pass (BEFORE), but the security camera that recorded you walking in can't change what happened (AFTER).

9. Exception Handling

Why? Programs break. A query returns zero rows when you expected one. Someone divides by zero. A value doesn't fit into a variable. Without exception handling, your entire block crashes and the user sees a cryptic Oracle error. With exception handling, you catch the error, handle it gracefully, and keep control.

🔍 Analogy
Exception handling is like the safety net under a trapeze artist. The artist (your code in BEGIN) does the risky work. If they fall (an error occurs), the net (EXCEPTION section) catches them instead of letting them hit the ground. Without the net, one mistake and the show is over.

The EXCEPTION section goes right before END; and catches specific errors using WHEN:

BEGIN
  SELECT salary INTO v_salary FROM employees WHERE employee_id = 9999;
  -- If employee 9999 doesn't exist, this crashes... unless we catch it:
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('No employee found.');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('Multiple rows returned.');
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('Cannot divide by zero.');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLCODE || ' — ' || SQLERRM);
END;
/
✅ The 4 predefined exceptions — memorize these
NO_DATA_FOUND — your SELECT INTO returned 0 rows. The employee/record doesn't exist.
TOO_MANY_ROWS — your SELECT INTO returned more than 1 row. It expected exactly one.
ZERO_DIVIDE — you tried to divide a number by zero.
VALUE_ERROR — wrong type or value doesn't fit (e.g., putting 'abc' into a NUMBER variable).

WHEN OTHERS — the catch-all. Catches any exception not handled above. Always put it LAST.

SQLCODE and SQLERRM — Debugging Tools

Inside WHEN OTHERS, you often want to know what went wrong:

💡 Exam tip
"Which keyword handles exceptions in PL/SQL?" → WHEN. Not CATCH (that's Java), not HANDLE, not RESCUE (that's Ruby). The syntax is always WHEN exception_name THEN.

User-Defined Exceptions

Oracle's predefined exceptions cover common cases, but what about your business rules? "Salary can't exceed 500000" isn't a database error — it's YOUR rule. You can create custom exceptions:

DECLARE
  e_salary_too_high EXCEPTION;    -- Step 1: declare it
  v_salary NUMBER := 600000;
BEGIN
  IF v_salary > 500000 THEN
    RAISE e_salary_too_high;       -- Step 2: raise it
  END IF;
EXCEPTION
  WHEN e_salary_too_high THEN    -- Step 3: handle it
    DBMS_OUTPUT.PUT_LINE('Salary exceeds maximum limit!');
END;
/
Question

Which keyword handles exceptions in PL/SQL?

  1. CATCH
  2. HANDLE
  3. WHEN
  4. RESCUE
C) WHEN. The syntax is WHEN exception_name THEN handler_code. PL/SQL doesn't use CATCH (Java), HANDLE, or RESCUE (Ruby). Every exception handler starts with WHEN.
Question

Exception raised when SELECT INTO returns more than one row?

  1. NO_DATA_FOUND
  2. VALUE_ERROR
  3. TOO_MANY_ROWS
  4. CURSOR_ALREADY_OPEN
C) TOO_MANY_ROWS. SELECT INTO expects exactly one row. Zero rows gives NO_DATA_FOUND. More than one gives TOO_MANY_ROWS. If you need multiple rows, use a cursor instead.
Question

What does SQLCODE return in an exception handler?

  1. Error text message
  2. Numeric error code
  3. Exception name
  4. Line number of error
B) SQLCODE returns the numeric error code (like -1, -1422, etc.). SQLERRM returns the text description. Together they give you the full picture of what went wrong.
Question

Which exception is raised for division by zero?

  1. VALUE_ERROR
  2. ARITHMETIC_ERROR
  3. ZERO_DIVIDE
  4. NUMERIC_OVERFLOW
C) ZERO_DIVIDE is the predefined Oracle exception for division by zero. ARITHMETIC_ERROR and NUMERIC_OVERFLOW don't exist as standard PL/SQL exceptions. VALUE_ERROR is for type mismatches, not division errors.

10. Packages

Why packages? As your database grows, you might have 20 procedures and 15 functions scattered everywhere. A package groups related procedures, functions, variables, and cursors into one organized unit — like putting related files into a folder instead of dumping everything on your desktop.

🔍 Analogy
A package is like a TV remote control. The specification (public interface) is the front of the remote — the buttons you can see and press. The body (implementation) is the circuit board inside — the actual code that makes things work. Users only interact with the buttons; they don't need to know what's inside.

Every package has two parts:

-- Specification: the public menu (what others can call)
CREATE OR REPLACE PACKAGE emp_pkg AS
  PROCEDURE hire(p_name VARCHAR2, p_sal NUMBER);
  FUNCTION get_salary(p_id NUMBER) RETURN NUMBER;
END emp_pkg;
/

-- Body: the actual implementation (hidden from callers)
CREATE OR REPLACE PACKAGE BODY emp_pkg AS
  PROCEDURE hire(p_name VARCHAR2, p_sal NUMBER) IS BEGIN
    INSERT INTO employees(first_name, salary) VALUES(p_name, p_sal);
  END;
  FUNCTION get_salary(p_id NUMBER) RETURN NUMBER IS v_sal NUMBER;
  BEGIN
    SELECT salary INTO v_sal FROM employees WHERE employee_id = p_id;
    RETURN v_sal;
  END;
END emp_pkg;
/

Calling package members: Use dot notation — emp_pkg.hire('Darshan', 50000);

✅ Spec vs Body
Specification = public contract. Change it and all dependent code must be recompiled.
Body = private implementation. You can change the body without affecting any calling code (as long as the spec stays the same).

This is encapsulation — the same idea as public/private in Java.
Question

Package SPECIFICATION contains:

  1. Implementation code
  2. Private variables only
  3. Public declarations — procedure/function signatures
  4. Only cursor definitions
C) The specification is the public interface — it declares what procedures and functions the package offers (their names, parameters, return types). The actual implementation code lives in the body. Think of the spec as the restaurant menu (what's available) and the body as the kitchen (where the food is made).

Practice Questions — PL/SQL (Mixed)

These questions mix all topics. If you can get 80%+ right, you're ready for the FA exam's PL/SQL section.

Question 1

What is the assignment operator in PL/SQL?

  1. =
  2. :=
  3. ==
  4. =>
B) := is the assignment operator. = is for comparison only. == doesn't exist in PL/SQL. => is used for named parameter notation in procedure calls.
Question 2

What does %ROWTYPE do?

  1. Returns the number of rows in a table
  2. Declares a variable matching one column's type
  3. Declares a record matching an entire row's structure
  4. Counts rows affected by a cursor
C) %ROWTYPE creates a record variable with the same structure as an entire table row — one field for each column. %TYPE (without ROW) matches a single column. %ROWCOUNT counts cursor rows.
Question 3

In a cursor FOR loop, which steps are handled AUTOMATICALLY by Oracle?

  1. Only OPEN
  2. OPEN and CLOSE
  3. OPEN, FETCH, and CLOSE
  4. Only FETCH
C) The cursor FOR loop automatically handles all three: OPEN (executes the query), FETCH (gets each row), and CLOSE (releases resources). That's why it's the preferred shortcut — you just write the loop body and Oracle handles the plumbing.
Question 4

Which DML operation has BOTH :OLD and :NEW available in a trigger?

  1. INSERT
  2. DELETE
  3. UPDATE
  4. SELECT
C) UPDATE is the only operation that has both a "before" state (:OLD) and an "after" state (:NEW). INSERT has only :NEW (nothing existed before). DELETE has only :OLD (nothing will exist after). SELECT doesn't fire triggers.
Question 5

What happens if you use SELECT INTO and the query returns 0 rows?

  1. The variable stays NULL — no error
  2. TOO_MANY_ROWS exception is raised
  3. NO_DATA_FOUND exception is raised
  4. The block silently exits
C) NO_DATA_FOUND is raised immediately. Unlike some languages where a missing result just returns null, PL/SQL treats zero rows from SELECT INTO as an error. You must either handle it with EXCEPTION or ensure the query will always return exactly one row.
Question 6

Can a function be called inside a SQL SELECT statement?

  1. No, only procedures can
  2. Yes, because functions return a value
  3. Only if it has no parameters
  4. Only if it returns VARCHAR2
B) Functions return a value, so SQL can use that value in a column, WHERE clause, etc. Procedures don't return values in a way SQL can use, so they can't appear in SELECT statements.
Question 7

What is the purpose of RAISE_APPLICATION_ERROR?

  1. Re-raises the current exception
  2. Creates a custom error with a user-defined message and error number
  3. Logs errors to a table
  4. Exits the current block silently
B) RAISE_APPLICATION_ERROR(error_number, message) creates a custom Oracle error. The error number must be between -20000 and -20999. This is how you enforce business rules — e.g., blocking a negative salary with a meaningful error message.