π― Objective: PL/SQL groups SQL statements with procedural syntax (variables, IF, loops, cursors) in a DECLARE β¦ BEGIN β¦ END; block β stored as a named schema object: a procedure (a statement on its own), a function (part of an expression, returns a value) or a trigger (fired by DML).
π¦ Core Components:IN β read only | OUT β write only | IN OUT β both.
β‘ Key Constraint:SELECT β¦ INTO must find exactly one row β none raises NO_DATA_FOUND, which ends the block unless an EXCEPTION handler catches it.
π How It Works
1. The block
Shape β DECLARE locals Β· BEGIN body Β· optional EXCEPTION WHEN β¦ THEN Β· END;.
Anchored type β v_Firstname student.first_name%type follows the column if it is retyped (VARCHAR2(20) β VARCHAR2(25)); a hard-coded VARCHAR2(20) would truncate or fail. Syntax: <variable> <table>.<column>%type.
Debug output β dbms_output.put_line('Data inserted β¦'); prints only after SQL> set serveroutput on;.
Body contents β IF-THEN-ELSE, loops, cursor fetching, and INSERT / UPDATE / DELETE.
2. Control flow
Branch β IF c THEN β¦ ELSIF c THEN β¦ ELSE β¦ END IF; (a nested IF inside ELSE is equivalent).
Simple loop β LOOP β¦ IF v_Counter > 50 THEN EXIT; END IF; END LOOP; β the exit test is yours to write.
While loop β WHILE v_Counter <= 50 LOOP β¦ END LOOP; β tested before each pass.
3. Cursors
Context area β memory Oracle allocates per statement: rows processed, a pointer to the parsed statement, and for a query the active set (its result rows).
Cursor β a handle (pointer) to that context area, declared as CURSOR c IS SELECT β¦; among the locals; it may use the procedureβs parameters in its WHERE.
Cursor FOR loop β FOR rec IN c LOOP β¦ rec.column β¦ END LOOP; β one pass per active-set row; rec is not declared.
4. Stored procedures
What β a self-contained schema object grouping SQL statements to perform one specific task; called with parameters, it acts, then returns to the caller.
Header β CREATE [OR REPLACE] PROCEDURE name (p [IN | OUT | IN OUT] type, β¦) AS β locals sit between AS and BEGIN, no DECLARE; parameters may be anchored (p_ID students.id%TYPE).
Run / remove β EXECUTE name(args); Β· DROP PROCEDURE name;.
Design rules β one focused task per procedure; never duplicate functionality the database language already provides.
5. Stored functions
Header β CREATE [OR REPLACE] FUNCTION name (β¦) RETURN type IS locals BEGIN β¦ RETURN value; END name;.
Call site β a procedure call is a statement by itself; a function call sits inside an expression β a SELECT list (SELECT department, ClassInfo(department, course) FROM classes;) or an IF test in another block.
RETURN β hands control back to the caller with a value; the first one reached ends the function.
6. Data dictionary
List β SELECT * FROM user_objects WHERE object_type = 'PROCEDURE'; Β· source β user_source Β· compile errors β user_errors, each filtered WHERE name = 'β¦'.
βοΈ Core Implementation
πΉ Parameter modes β ModeTest
What each mode allows
CREATE OR REPLACE PROCEDURE ModeTest ( p_InParameter IN NUMBER, p_OutParameter OUT NUMBER, p_InOutParameter IN OUT NUMBER) AS v_LocalVariable NUMBER;BEGIN v_LocalVariable := p_InParameter; -- legal: read an IN p_InParameter := 7; -- ILLEGAL: write an IN p_OutParameter := 7; -- legal: write an OUT v_LocalVariable := p_OutParameter; -- illegal per the slides: read an OUT v_LocalVariable := p_InOutParameter; -- legal p_InOutParameter := 7; -- legalEND ModeTest;
π‘ Common Mistake:Writing to an IN parameter β a compile error, not a silent copy; take a local copy first.
πΉ Exception as control flow β credit_account
Update if the row exists, insert if NO_DATA_FOUND
CREATE PROCEDURE credit_account (acct NUMBER, credit NUMBER) AS old_balance NUMBER; new_balance NUMBER;BEGIN SELECT balance INTO old_balance FROM accounts WHERE acct_id = acct FOR UPDATE OF balance; -- lock the row being changed new_balance := old_balance + credit; UPDATE accounts SET balance = new_balance WHERE acct_id = acct;EXCEPTION WHEN NO_DATA_FOUND THEN -- account absent: create it INSERT INTO accounts (acct_id, balance) VALUES (acct, credit);END credit_account;
π‘ Common Mistake:Testing existence with an IF after the SELECT β¦ INTO β execution never reaches it; the failed fetch jumps straight to EXCEPTION. The active-DW triggers get the same find-or-insert with select count(*) into IsFound, which never raises.
πΉ Cursor loop inside a procedure β CheckStudentCompletion
One pass per student
CREATE OR REPLACE PROCEDURE CheckStudentCompletion AS CURSOR c_student IS SELECT id, last_name, total_current_credits FROM students;BEGIN FOR v_StudentRecord IN c_student LOOP IF v_StudentRecord.total_current_credits > 300 THEN INSERT INTO temp_table (desc_col) VALUES (v_StudentRecord.id || v_StudentRecord.last_name || ' final semester student!'); END IF; END LOOP;END CheckStudentCompletion;
πΉ Function and its caller β ClassInfo
Function body + a procedure that calls it
CREATE OR REPLACE FUNCTION ClassInfo ( p_Department classes.department%TYPE, p_Course classes.course%TYPE) RETURN VARCHAR2 IS v_CurrentStudents NUMBER; v_MaxStudents NUMBER; v_PercentFull NUMBER;BEGIN SELECT current_students, max_students INTO v_CurrentStudents, v_MaxStudents FROM classes WHERE department = p_Department AND course = p_Course; v_PercentFull := v_CurrentStudents / v_MaxStudents * 100; IF v_PercentFull = 100 THEN RETURN 'Full'; ELSIF v_PercentFull > 80 THEN RETURN 'Some Room'; ELSIF v_PercentFull > 60 THEN RETURN 'More Room'; ELSIF v_PercentFull > 0 THEN RETURN 'Lots of Room'; ELSE RETURN 'Empty'; END IF;END ClassInfo;CREATE OR REPLACE PROCEDURE RecordFullClasses AS CURSOR c_Classes IS SELECT department, course FROM classes;BEGIN FOR v_ClassRecord IN c_Classes LOOP IF ClassInfo(v_ClassRecord.department, v_ClassRecord.course) = 'Some Room' THEN INSERT INTO temp_table (char_col) VALUES (v_ClassRecord.department || ' ' || v_ClassRecord.course || ' is almost full!'); END IF; END LOOP;END RecordFullClasses;
βοΈ Core Decision Matrix
Object
Invoked by
Header
Hands back
Typical job
Procedure
an explicit call β EXECUTE, or a statement in another block
CREATE PROCEDURE β¦ AS
OUT / IN OUT parameters
one focused task (AddNewStudent)
Function
an expression β a SELECT list, an IF test
CREATE FUNCTION β¦ RETURN type IS
one value via RETURN
a computed value or label per row (ClassInfo)
Trigger
the system, on INSERT / UPDATE / DELETE of its table
CREATE TRIGGER β¦ {BEFORE | AFTER | INSTEAD OF} β¦ ON t
When It Flips: need the result inside a query β function; need several results, or the work itself is the point β procedure with OUT parameters; no one should have to remember to call it β trigger.
Boundary reading β strict > sends an exact 80% or 60%down a band; ELSIF stops at the first true test, so order the tests from the highest threshold down.
β οΈ Common Mistakes
π‘ DECLARE inside a procedure or function β their locals go after AS / IS; DECLARE belongs to anonymous blocks and triggers.
π‘ Unquoted object_type = PROCEDURE β the slideβs form is read as a column name and fails; quote it: 'PROCEDURE'.
π§ Active Recall
ClassInfo can appear in a SELECT list; AddNewStudent cannot. Why?
Answer
Short answer: a function returns a value and is called as part of an expression; a procedure returns nothing and is a statement on its own.
Why:A SELECT list is a list of expressions β each item must evaluate to a value per row, which RETURN VARCHAR2 guarantees. A procedure hands results back through parameters β there is no value to place in a column, so it is run with EXECUTE or as a line in another block.
Why declare v_Firstname student.first_name%type instead of VARCHAR2(20)?
Answer
Short answer:%type ties the variable to the columnβs current definition, so a later ALTER cannot break the code.
Why:Hard-coded types go stale β widening first_name to VARCHAR2(25) leaves a VARCHAR2(20) variable too small for new values. Anchoring is resolved at compile time β recompiling picks up the new type with no edit. Same idea in triggers β Degree Student.DegreeCode%type in Active Data Warehousing.
credit_account(999, 50) runs when account 999 does not exist. Trace what happens.
Answer
Short answer: the SELECT β¦ INTO finds no row, raises NO_DATA_FOUND, and the handler inserts account 999 with balance 50.
Why:The fetch is the existence test β the UPDATE line is skipped entirely, because control jumps to EXCEPTION the moment the fetch fails. The handler is the else-branch β insert-if-absent, update-if-present in one procedure with no IF.