This repository contains comprehensive PL/SQL examples demonstrating various techniques for working with databases in Oracle. Each example is organized into separate SQL files with clear documentation.
- %ROWTYPE - Whole Row Fetching
- RECORD TYPE - Custom Columns
- Basic Cursor - Multiple Rows
- Cursor with Parameter
- FOR LOOP with Cursor
- Exception Handling with Custom Error
File: 01_rowtype_example.sql
%ROWTYPE is a PL/SQL attribute that allows you to declare a record variable that represents a whole row from a database table. It automatically includes all columns from the specified table.
- When you need all columns from a table
- When you want to avoid declaring each variable manually
- When the table structure might change and you want automatic updates
SELECT INTOonly works when exactly one row is returned- If no rows are found:
NO_DATA_FOUNDexception is raised - If multiple rows are found:
TOO_MANY_ROWSexception is raised - Always use exception handling with
SELECT INTO
DECLARE
emp_rec employees%ROWTYPE;
BEGIN
SELECT *
INTO emp_rec
FROM employees
WHERE employee_id = 200;
DBMS_OUTPUT.PUT_LINE(emp_rec.last_name || ' ' || emp_rec.salary);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No data found');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Too many rows');
END;Access individual fields using dot notation: record_variable.column_name
File: 02_record_type_example.sql
Custom RECORD types allow you to define a structure with only the columns you need, giving you control over field names and data types.
- When you only need some columns from a table
- When you want custom field names
- When you need to combine columns from multiple tables
- When you want better code readability with meaningful names
- Define the record structure using
TYPE ... IS RECORD - Use
%TYPEto inherit data types from table columns SELECT INTOstill requires exactly one row- More flexible than
%ROWTYPEbut requires manual definition
DECLARE
TYPE employee_record IS RECORD(
v_last employees.last_name%TYPE,
v_sal employees.salary%TYPE
);
emp_acces employee_record;
BEGIN
SELECT last_name, salary
INTO emp_acces
FROM employees
WHERE employee_id = 200;
DBMS_OUTPUT.PUT_LINE(emp_acces.v_last || ' ' || emp_acces.v_sal);
END;- Better performance (only fetches needed columns)
- Custom naming conventions
- Can combine multiple tables
- More readable code
File: 03_basic_cursor.sql
Cursors are used to process multiple rows from a query result. Unlike SELECT INTO, cursors can handle zero, one, or many rows.
- When you need to process multiple rows
- When the result set can be empty or have many rows
- When you need to iterate through query results
- Declare the cursor with a SELECT statement
- OPEN the cursor before fetching
- FETCH rows one at a time in a loop
- CLOSE the cursor when done
- Use
%NOTFOUNDto check for end of result set
- DECLARE - Define the cursor and SQL query
- OPEN - Execute the query and create result set
- FETCH - Retrieve one row at a time
- CLOSE - Release resources
DECLARE
CURSOR books_cur IS
SELECT * FROM bk_books
WHERE authorid = 'J100';
books_rec bk_books%ROWTYPE;
BEGIN
OPEN books_cur;
LOOP
FETCH books_cur INTO books_rec;
EXIT WHEN books_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(books_rec.title);
END LOOP;
CLOSE books_cur;
END;%NOTFOUND- Returns TRUE if last fetch returned no row%FOUND- Returns TRUE if last fetch returned a row%ROWCOUNT- Returns number of rows fetched so far%ISOPEN- Returns TRUE if cursor is open
File: 04_cursor_with_parameter.sql
Parameterized cursors accept parameters when opened, making them reusable with different values.
- When you need to reuse a cursor with different criteria
- When you want to make your cursor more flexible
- When you need to pass values dynamically
- Define parameters in the cursor declaration
- Pass values when opening the cursor:
OPEN cursor_name(value) - Parameters are used in the WHERE clause
- More efficient than using substitution variables
DECLARE
CURSOR books_cur(p_authorid bk_books.authorid%TYPE) IS
SELECT * FROM bk_books
WHERE authorid = p_authorid;
books_rec bk_books%ROWTYPE;
BEGIN
OPEN books_cur('J100');
LOOP
FETCH books_cur INTO books_rec;
EXIT WHEN books_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(books_rec.title);
END LOOP;
CLOSE books_cur;
END;- Reusability - Same cursor, different parameters
- Performance - Better than dynamic SQL for repeated queries
- Type Safety - Parameters use
%TYPEfor type checking - Flexibility - Easy to change search criteria
File: 05_for_loop_cursor.sql
The FOR LOOP cursor is the simplest and most efficient way to iterate through cursor results. It automatically handles OPEN, FETCH, and CLOSE operations.
- When you want the simplest cursor implementation
- When you don't need fine-grained control over cursor operations
- Recommended for most cursor operations
- When you want automatic exception handling
- No need to OPEN, FETCH, or CLOSE - handled automatically
- No need to declare record variable - implicitly created
- Automatic exception handling
- Cleaner and more readable code
DECLARE
CURSOR books_cur(p_authorid bk_books.authorid%TYPE) IS
SELECT * FROM bk_books
WHERE authorid = p_authorid;
BEGIN
FOR book_rec IN books_cur('J100') LOOP
DBMS_OUTPUT.PUT_LINE(book_rec.title);
END LOOP;
END;- Less code - No manual OPEN/FETCH/CLOSE
- Automatic cleanup - Cursor closed even if exception occurs
- Better performance - Oracle optimizes FOR LOOP cursors
- Error handling - Automatically handles cursor errors
| Feature | Manual Cursor | FOR LOOP Cursor |
|---|---|---|
| Code Lines | More | Fewer |
| OPEN/FETCH/CLOSE | Manual | Automatic |
| Record Declaration | Required | Implicit |
| Exception Handling | Manual | Automatic |
| Performance | Good | Better |
File: 06_exception_handling.sql
Exception handling allows you to catch and handle database errors gracefully. PRAGMA EXCEPTION_INIT associates Oracle error codes with user-defined exceptions.
- When you want to handle specific Oracle errors
- When you need user-friendly error messages
- When you want to prevent program crashes
- When dealing with integrity constraints
- Define custom exception:
exception_name EXCEPTION - Associate with error code:
PRAGMA EXCEPTION_INIT(exception_name, error_code) - ORA-02292 (-2292): Foreign key constraint violated
- Always use exception handlers for data manipulation
- -0001 - Unique constraint violated
- -01403 - No data found
- -01422 - Too many rows
- -02291 - Foreign key constraint violated (parent key not found)
- -02292 - Foreign key constraint violated (child record exists)
DECLARE
e_user_book EXCEPTION;
PRAGMA EXCEPTION_INIT(e_user_book, -2292);
v_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_count
FROM bk_books
WHERE authorid = 'J100';
DELETE FROM bk_author
WHERE authorid = 'J100';
EXCEPTION
WHEN e_user_book THEN
DBMS_OUTPUT.PUT_LINE('Cannot delete. Author has ' || v_count || ' book(s).');
END;- Always handle NO_DATA_FOUND when using SELECT INTO
- Always handle TOO_MANY_ROWS when using SELECT INTO
- Handle constraint violations for DELETE/UPDATE operations
- Provide meaningful error messages to users
- Log errors for debugging purposes
- Oracle Database (11g or higher)
- SQL*Plus or SQL Developer
- Appropriate table structures (employees, bk_books, bk_author, s_student, s_subject)
-
Set up your environment:
SET SERVEROUTPUT ON; SET VERIFY OFF; -- For substitution variables
-
Run individual examples:
@01_rowtype_example.sql @02_record_type_example.sql @03_basic_cursor.sql @04_cursor_with_parameter.sql @05_for_loop_cursor.sql @06_exception_handling.sql
-
Modify table names and values according to your database schema
bookid(Primary Key)titleauthorid(Foreign Key)categorypubdate
authorid(Primary Key)fnamelname
employee_id(Primary Key)last_namesalary
studnr(Primary Key)surname
DIP_CODE(Primary Key)- (Other columns with foreign key constraints)
| Method | Use Case | Rows Handled | Complexity | Performance |
|---|---|---|---|---|
| %ROWTYPE | Single row, all columns | 1 | Low | High |
| RECORD TYPE | Single row, some columns | 1 | Medium | High |
| Basic Cursor | Multiple rows, full control | Many | High | Medium |
| Cursor + Parameter | Multiple rows, reusable | Many | High | Medium |
| FOR LOOP Cursor | Multiple rows, simple | Many | Low | High |
Use %ROWTYPE when:
- You need exactly one row
- You need all columns
- Table structure may change
Use RECORD TYPE when:
- You need exactly one row
- You need only some columns
- You want custom field names
Use Cursor when:
- You need multiple rows
- Result set can be empty
- You need fine-grained control
Use FOR LOOP Cursor when:
- You need multiple rows
- You want simple, clean code
- Recommended for most cases
Problem: SELECT INTO finds no rows
Solution: Always use exception handling
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No data found');Problem: SELECT INTO finds multiple rows
Solution: Use a cursor or add more specific WHERE clause
-- Use cursor instead of SELECT INTO
CURSOR cur IS SELECT * FROM table WHERE condition;Problem: Forgetting to close cursor
Solution: Use FOR LOOP cursor or always close in exception handler
EXCEPTION
WHEN OTHERS THEN
IF cursor_name%ISOPEN THEN
CLOSE cursor_name;
END IF;Problem: Typo "PROGMA" instead of "PRAGMA"
Solution: Always use "PRAGMA EXCEPTION_INIT"
- Always use exception handling with SELECT INTO
- Prefer FOR LOOP cursors over manual cursor management
- Use %TYPE and %ROWTYPE for type safety
- Close cursors explicitly or use FOR LOOP
- Provide meaningful error messages
- Use parameters for reusable cursors
- Test with edge cases (no rows, multiple rows)
- Document your code with comments
| Feature | SELECT INTO | Cursor |
|---|---|---|
| Rows | Exactly 1 | 0, 1, or Many |
| Exception | Required | Optional |
| Performance | Faster (single fetch) | Slower (multiple fetches) |
| Use Case | Single row lookup | Multiple row processing |
| Feature | %ROWTYPE | RECORD TYPE |
|---|---|---|
| Columns | All columns | Selected columns |
| Maintenance | Automatic | Manual |
| Performance | Fetches all | Fetches selected |
| Flexibility | Low | High |
| Naming | Table column names | Custom names |
Feel free to submit issues, fork the repository, and create pull requests for any improvements.
This repository is for educational purposes. Use the code examples freely in your learning and projects.
Created for PL/SQL learning and practice.