SQLMentor // cheat sheet

PL/SQL Cheat Sheet

Every PL/SQL pattern you reach for daily, on one page: block structure, cursors, exception handling, procedures/functions/packages, triggers, collections, and the BULK COLLECT/FORALL performance pattern. Bookmark it, or work through the underlying concepts in the free PL/SQL tutorial.

Block structure

Every PL/SQL block has the same three parts; DECLARE and EXCEPTION are optional.

DECLARE
  v_salary NUMBER;
BEGIN
  SELECT salary INTO v_salary FROM employees WHERE employee_id = 100;
  DBMS_OUTPUT.PUT_LINE(v_salary);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('No such employee');
END;
/

Common data types

TypeStores
NUMBER(p,s)fixed or floating-point numbers
VARCHAR2(n)variable-length string, up to 32767 bytes in PL/SQL
DATEdate and time, second precision
TIMESTAMPdate and time with fractional seconds
BOOLEANTRUE / FALSE / NULL -- PL/SQL only, not a SQL column type
%TYPEanchors a variable's type to a column, e.g. employees.salary%TYPE
%ROWTYPEanchors a record's structure to a table or cursor's row

Control structures

ConstructSyntax
IFIF cond THEN ... ELSIF cond THEN ... ELSE ... END IF;
Simple CASECASE v_grade WHEN 'A' THEN ... ELSE ... END CASE;
Basic LOOPLOOP ... EXIT WHEN cond; END LOOP;
WHILE LOOPWHILE cond LOOP ... END LOOP;
FOR LOOPFOR i IN 1..10 LOOP ... END LOOP;
Cursor FOR LOOPFOR rec IN (SELECT * FROM employees) LOOP ... END LOOP;

Cursors

An implicit cursor (SQL%...) is created automatically for every DML statement; an explicit cursor gives you control over a multi-row query.

Attribute / syntaxMeaning
CURSOR c IS SELECT ...;declare an explicit cursor
OPEN c; FETCH c INTO ...; CLOSE c;the open/fetch/close lifecycle
c%FOUND / c%NOTFOUNDdid the last FETCH return a row?
c%ROWCOUNTrows fetched so far
c%ISOPENis the cursor currently open?
SQL%ROWCOUNTrows affected by the last implicit DML statement
CURSOR c(p_dept NUMBER) IS ...a parameterized cursor
FOR UPDATE / WHERE CURRENT OF clock fetched rows, then update/delete the current one

Exception handling

ExceptionRaised when
NO_DATA_FOUNDa SELECT INTO returns zero rows
TOO_MANY_ROWSa SELECT INTO returns more than one row
DUP_VAL_ON_INDEXa unique constraint/index is violated
VALUE_ERRORa conversion or size/precision error
ZERO_DIVIDEdivision by zero
OTHERScatch-all -- always list it last
RAISE_APPLICATION_ERROR(-20001, 'msg')raise a custom error with a user-defined number/message
BEGIN
  ...
EXCEPTION
  WHEN NO_DATA_FOUND THEN NULL;
  WHEN OTHERS THEN
    RAISE_APPLICATION_ERROR(-20001, 'Unexpected: ' || SQLERRM);
END;

Procedures & functions

A function must return exactly one value and can be called from inside a SQL expression; a procedure performs an action and is invoked with CALL/EXEC.

CREATE OR REPLACE PROCEDURE give_raise(p_emp_id IN NUMBER, p_pct IN NUMBER) IS
BEGIN
  UPDATE employees SET salary = salary * (1 + p_pct/100) WHERE employee_id = p_emp_id;
END;
/

CREATE OR REPLACE FUNCTION get_annual_salary(p_emp_id NUMBER) RETURN NUMBER IS
  v_salary NUMBER;
BEGIN
  SELECT salary * 12 INTO v_salary FROM employees WHERE employee_id = p_emp_id;
  RETURN v_salary;
END;
/

Packages

A package groups related procedures, functions, and variables behind a public spec, hiding the implementation in the body.

CREATE OR REPLACE PACKAGE emp_pkg IS
  PROCEDURE give_raise(p_emp_id NUMBER, p_pct NUMBER);
  FUNCTION get_annual_salary(p_emp_id NUMBER) RETURN NUMBER;
END emp_pkg;
/

CREATE OR REPLACE PACKAGE BODY emp_pkg IS
  PROCEDURE give_raise(p_emp_id NUMBER, p_pct NUMBER) IS BEGIN
    UPDATE employees SET salary = salary * (1 + p_pct/100) WHERE employee_id = p_emp_id;
  END;
  FUNCTION get_annual_salary(p_emp_id NUMBER) RETURN NUMBER IS
    v_salary NUMBER;
  BEGIN
    SELECT salary * 12 INTO v_salary FROM employees WHERE employee_id = p_emp_id;
    RETURN v_salary;
  END;
END emp_pkg;
/

Triggers

ClauseMeaning
BEFORE / AFTERfire before or after the triggering DML statement
INSTEAD OFreplaces the DML entirely -- used on views
FOR EACH ROWrow-level trigger; fires once per affected row
:NEW / :OLDthe new/old value of a column in a row-level trigger
WHEN (condition)restricts a row-level trigger to matching rows
CREATE OR REPLACE TRIGGER trg_salary_audit
  BEFORE UPDATE OF salary ON employees
  FOR EACH ROW
BEGIN
  INSERT INTO salary_audit (employee_id, old_salary, new_salary, changed_on)
  VALUES (:OLD.employee_id, :OLD.salary, :NEW.salary, SYSDATE);
END;
/

Collections

TypeCharacteristics
Associative array (INDEX BY)key-value pairs, indexed by integer or string, not persisted in the database
Nested tableunbounded, dense-then-sparse after deletion, can be a column type
VARRAYfixed maximum size, always dense, ordering preserved
TYPE t_salary_tab IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
v_salaries t_salary_tab;
v_salaries(1) := 5000;

BULK COLLECT & FORALL

Row-by-row PL/SQL loops over SQL are slow because of the repeated context switch between the SQL and PL/SQL engines. BULK COLLECT and FORALL batch that switch into one round trip.

DECLARE
  TYPE t_ids IS TABLE OF employees.employee_id%TYPE;
  v_ids t_ids;
BEGIN
  SELECT employee_id BULK COLLECT INTO v_ids FROM employees WHERE department_id = 60;

  FORALL i IN v_ids.FIRST .. v_ids.LAST
    UPDATE employees SET salary = salary * 1.1 WHERE employee_id = v_ids(i);
END;
/

Dynamic SQL

Use EXECUTE IMMEDIATE when the table/column name or statement shape isn't known until runtime. Always bind values with USING instead of concatenating them -- concatenation is the classic SQL-injection mistake.

EXECUTE IMMEDIATE 'SELECT salary FROM ' || p_table_name || ' WHERE employee_id = :1'
  INTO v_salary USING p_emp_id;

Practise what's on this page

Run any query on this page live against a real schema — free, no signup, results in under a second.

Open the SQL editor →

Frequently asked questions

Is this PL/SQL cheat sheet free to use?
Yes. Every reference table here, the underlying tutorial, and the free SQL editor are completely free with no sign-up.
What's the difference between a procedure and a function?
A function must RETURN exactly one value and can be used inside a SQL expression (SELECT, WHERE). A procedure performs an action and doesn't return a value through RETURN — it communicates results, if any, through OUT parameters, and is called with CALL or EXEC.
Why use BULK COLLECT and FORALL instead of a simple loop?
A row-by-row PL/SQL loop switches between the PL/SQL and SQL engines on every single row, which is slow at scale. BULK COLLECT and FORALL batch many rows into one context switch, often cutting execution time dramatically on large datasets.
Can I print or save this cheat sheet?
Yes — it's a normal web page, so your browser's print-to-PDF or bookmark feature works fine. There is no separate download required.
Where can I practise PL/SQL?
The PL/SQL tutorial covers every topic on this page in depth, and the free in-browser SQL editor lets you run queries live.