Skip to main content

Oracle PL/SQL Common Mistakes

This guide covers common Oracle PL/SQL programming mistakes and how to avoid them, with practical solutions and examples.

🚨 Most Common Oracle Errors

1. SQL Injection

Using string concatenation in dynamic SQL leading to security vulnerabilities.

Problem

-- Bad: Vulnerable to SQL injection
CREATE OR REPLACE PROCEDURE get_student_by_name(
p_name IN VARCHAR2
) AS
v_sql VARCHAR2(1000);
v_student students%ROWTYPE;
BEGIN
v_sql := 'SELECT * FROM students WHERE name = ''' || p_name || '''';
EXECUTE IMMEDIATE v_sql INTO v_student;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END;
/

-- Vulnerable input: ' OR '1'='1
-- This would return all students!

Solutions

-- Good: Use bind variables
CREATE OR REPLACE PROCEDURE get_student_by_name_safe(
p_name IN students.name%TYPE
) AS
v_sql VARCHAR2(1000);
v_student students%ROWTYPE;
BEGIN
v_sql := 'SELECT * FROM students WHERE name = :student_name';
EXECUTE IMMEDIATE v_sql INTO v_student
USING p_name;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END;
/

-- Better: Use static SQL when possible
CREATE OR REPLACE PROCEDURE get_student_by_name_static(
p_name IN students.name%TYPE
) AS
v_student students%ROWTYPE;
BEGIN
SELECT * INTO v_student
FROM students
WHERE name = p_name;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Student not found: ' || p_name);
END;
/

2. Cursor Memory Leaks

Not closing cursors properly or leaving them open.

Problem

-- Bad: Cursor not closed
CREATE OR REPLACE PROCEDURE process_students_bad AS
CURSOR student_cursor IS
SELECT student_id, name FROM students;

v_student student_cursor%ROWTYPE;
BEGIN
OPEN student_cursor;
FETCH student_cursor INTO v_student;

-- Process student
DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);

-- Forgot to close cursor!
END;
/

-- Bad: Exception without cleanup
CREATE OR REPLACE PROCEDURE process_students_exception AS
CURSOR student_cursor IS
SELECT student_id, name FROM students;

v_student student_cursor%ROWTYPE;
BEGIN
OPEN student_cursor;
LOOP
FETCH student_cursor INTO v_student;
EXIT WHEN student_cursor%NOTFOUND;

-- This might raise an exception
IF v_student.student_id = 999 THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid student ID');
END IF;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END LOOP;

CLOSE student_cursor;
END;
/

Solutions

-- Good: Proper cursor management
CREATE OR REPLACE PROCEDURE process_students_good AS
CURSOR student_cursor IS
SELECT student_id, name FROM students;

v_student student_cursor%ROWTYPE;
BEGIN
OPEN student_cursor;
LOOP
FETCH student_cursor INTO v_student;
EXIT WHEN student_cursor%NOTFOUND;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END LOOP;

CLOSE student_cursor;
END;
/

-- Better: Exception handling with cleanup
CREATE OR REPLACE PROCEDURE process_students_safe AS
CURSOR student_cursor IS
SELECT student_id, name FROM students;

v_student student_cursor%ROWTYPE;
BEGIN
OPEN student_cursor;
LOOP
FETCH student_cursor INTO v_student;
EXIT WHEN student_cursor%NOTFOUND;

IF v_student.student_id = 999 THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid student ID');
END IF;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END LOOP;

CLOSE student_cursor;

EXCEPTION
WHEN OTHERS THEN
IF student_cursor%ISOPEN THEN
CLOSE student_cursor;
END IF;
RAISE;
END;
/

-- Best: Use implicit cursor
CREATE OR REPLACE PROCEDURE process_students_implicit AS
BEGIN
FOR v_student IN (SELECT student_id, name FROM students) LOOP
IF v_student.student_id = 999 THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid student ID');
END IF;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END LOOP;
END;
/

3. Transaction Management Errors

Improper commit/rollback handling leading to data inconsistency.

Problem

-- Bad: Commit in loop (performance issues)
CREATE OR REPLACE PROCEDURE update_grades_bad AS
BEGIN
FOR v_student IN (SELECT student_id FROM students) LOOP
UPDATE grades
SET grade = 'A'
WHERE student_id = v_student.student_id;

COMMIT; -- Bad: Commit in loop!
END LOOP;
END;
/

-- Bad: No transaction management
CREATE OR REPLACE PROCEDURE transfer_funds_bad(
p_from_account IN NUMBER,
p_to_account IN NUMBER,
p_amount IN NUMBER
) AS
BEGIN
UPDATE accounts
SET balance = balance - p_amount
WHERE account_id = p_from_account;

-- If this fails, first update remains committed!
UPDATE accounts
SET balance = balance + p_amount
WHERE account_id = p_to_account;

COMMIT;
END;
/

Solutions

-- Good: Single transaction
CREATE OR REPLACE PROCEDURE update_grades_good AS
BEGIN
FOR v_student IN (SELECT student_id FROM students) LOOP
UPDATE grades
SET grade = 'A'
WHERE student_id = v_student.student_id;
END LOOP;

COMMIT; -- Single commit after all updates
END;
/

-- Better: Batch processing for large datasets
CREATE OR REPLACE PROCEDURE update_grades_batch AS
v_batch_size CONSTANT NUMBER := 1000;
v_processed NUMBER := 0;
BEGIN
FOR v_student IN (SELECT student_id FROM students) LOOP
UPDATE grades
SET grade = 'A'
WHERE student_id = v_student.student_id;

v_processed := v_processed + 1;

IF MOD(v_processed, v_batch_size) = 0 THEN
COMMIT; -- Commit every 1000 records
END IF;
END LOOP;

IF MOD(v_processed, v_batch_size) != 0 THEN
COMMIT; -- Final commit
END IF;
END;
/

-- Best: Proper transaction management
CREATE OR REPLACE PROCEDURE transfer_funds_safe(
p_from_account IN NUMBER,
p_to_account IN NUMBER,
p_amount IN NUMBER
) AS
v_from_balance NUMBER;
v_to_balance NUMBER;
BEGIN
-- Start transaction
SAVEPOINT start_transfer;

-- Check balances
SELECT balance INTO v_from_balance
FROM accounts
WHERE account_id = p_from_account;

IF v_from_balance < p_amount THEN
RAISE_APPLICATION_ERROR(-20001, 'Insufficient funds');
END IF;

-- Perform transfers
UPDATE accounts
SET balance = balance - p_amount
WHERE account_id = p_from_account;

UPDATE accounts
SET balance = balance + p_amount
WHERE account_id = p_to_account;

COMMIT;

EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO start_transfer;
DBMS_OUTPUT.PUT_LINE('Transfer failed: ' || SQLERRM);
END;
/

4. Performance Issues

Inefficient queries and poor index usage.

Problem

-- Bad: Functions on indexed columns
CREATE OR REPLACE PROCEDURE get_students_by_name_bad AS
v_student students%ROWTYPE;
BEGIN
FOR v_student IN (
SELECT * FROM students
WHERE UPPER(name) = 'JOHN SMITH' -- Can't use index on name
) LOOP
DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END LOOP;
END;
/

-- Bad: Row-by-row processing
CREATE OR REPLACE PROCEDURE calculate_totals_bad AS
v_total NUMBER;
BEGIN
FOR v_student IN (SELECT student_id FROM students) LOOP
SELECT SUM(amount) INTO v_total
FROM payments
WHERE student_id = v_student.student_id;

UPDATE students
SET total_payments = v_total
WHERE student_id = v_student.student_id;
END LOOP;
END;
/

Solutions

-- Good: Use indexes effectively
CREATE OR REPLACE PROCEDURE get_students_by_name_good AS
v_student students%ROWTYPE;
BEGIN
FOR v_student IN (
SELECT * FROM students
WHERE name = 'John Smith' -- Can use index on name
) LOOP
DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END LOOP;
END;
/

-- Better: Function-based index
CREATE INDEX idx_students_upper_name ON students(UPPER(name));

CREATE OR REPLACE PROCEDURE get_students_by_name_indexed AS
v_student students%ROWTYPE;
BEGIN
FOR v_student IN (
SELECT * FROM students
WHERE UPPER(name) = 'JOHN SMITH' -- Can use function-based index
) LOOP
DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);
END LOOP;
END;
/

-- Best: Bulk operations
CREATE OR REPLACE PROCEDURE calculate_totals_bulk AS
BEGIN
UPDATE students s
SET total_payments = (
SELECT SUM(p.amount)
FROM payments p
WHERE p.student_id = s.student_id
);

COMMIT;
END;
/

5. Exception Handling Errors

Poor exception handling leading to silent failures or information leakage.

Problem

-- Bad: Catching all exceptions
CREATE OR REPLACE PROCEDURE process_student_bad(
p_student_id IN NUMBER
) AS
v_student students%ROWTYPE;
BEGIN
SELECT * INTO v_student
FROM students
WHERE student_id = p_student_id;

-- Process student
DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);

EXCEPTION
WHEN OTHERS THEN
NULL; -- Silent failure!
END;
/

-- Bad: Information leakage
CREATE OR REPLACE PROCEDURE login_user_bad(
p_username IN VARCHAR2,
p_password IN VARCHAR2
) AS
v_user_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_user_count
FROM users
WHERE username = p_username AND password = p_password;

IF v_user_count = 1 THEN
DBMS_OUTPUT.PUT_LINE('Login successful');
ELSE
DBMS_OUTPUT.PUT_LINE('Login failed: User ' || p_username || ' not found');
END IF;
END;
/

Solutions

-- Good: Specific exception handling
CREATE OR REPLACE PROCEDURE process_student_good(
p_student_id IN NUMBER
) AS
v_student students%ROWTYPE;
BEGIN
SELECT * INTO v_student
FROM students
WHERE student_id = p_student_id;

DBMS_OUTPUT.PUT_LINE('Student: ' || v_student.name);

EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Student not found: ' || p_student_id);
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Multiple students found: ' || p_student_id);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error processing student: ' || SQLERRM);
RAISE;
END;
/

-- Better: Secure error messages
CREATE OR REPLACE PROCEDURE login_user_secure(
p_username IN VARCHAR2,
p_password IN VARCHAR2
) AS
v_user_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_user_count
FROM users
WHERE username = p_username AND password = p_password;

IF v_user_count = 1 THEN
DBMS_OUTPUT.PUT_LINE('Login successful');
ELSE
DBMS_OUTPUT.PUT_LINE('Invalid username or password');
END IF;

EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Login system error');
-- Log actual error for debugging
INSERT INTO error_log (error_time, error_message)
VALUES (SYSDATE, SQLERRM);
END;
/

🔧 Data Type Mistakes

6. Implicit Type Conversion

Relying on implicit type conversions leading to unexpected behavior.

Problem

-- Bad: Implicit conversion
CREATE OR REPLACE PROCEDURE process_numbers_bad AS
v_number VARCHAR2(10) := '123';
v_result NUMBER;
BEGIN
v_result := v_number + 456; -- Implicit conversion
DBMS_OUTPUT.PUT_LINE('Result: ' || v_result);
END;
/

Solutions

-- Good: Explicit conversion
CREATE OR REPLACE PROCEDURE process_numbers_good AS
v_number VARCHAR2(10) := '123';
v_result NUMBER;
BEGIN
v_result := TO_NUMBER(v_number) + 456; -- Explicit conversion
DBMS_OUTPUT.PUT_LINE('Result: ' || v_result);
END;
/

🛠️ Debugging Tips

Common Debugging Strategies

  1. DBMS_OUTPUT: Add debug output statements
  2. Exception Logging: Log errors for later analysis
  3. SQL Trace: Use Oracle's trace facilities
  4. Explain Plan: Analyze query performance
  5. Error Tables: Create custom error logging

Debugging Checklist

  • Use bind variables instead of string concatenation
  • Close all cursors properly
  • Handle exceptions appropriately
  • Use proper transaction management
  • Optimize queries for index usage
  • Avoid row-by-row processing
  • Use explicit type conversions
  • Log errors for debugging

🔗 Common Programming Mistakes

📍 Visit Us

🏫 VD Computer Tuition Surat

VD Computer Tuition
📍 Address
2/66 Faram Street, Rustompura
Surat395002, Gujarat, India
📞 Phone / WhatsApp
+91 84604 41384
🌐 Website

Computer Classes & Tuition — Areas We Serve in Surat

AdajanAlthanAmroliAthwaAthwalinesBhagalBhatarBhestanCanal RoadChowkCitylightDumasGaurav PathGhod Dod RoadHaziraJahangirpuraKamrejKapodraKatargamLimbayatMagdallaMajura GateMota VarachhaNanpuraNew CitylightOlpadPalPandesaraParle PointPiplodPunaRanderRing RoadRustampuraSachinSalabatpuraSarthanaSosyo CircleUdhnaVarachhaVed RoadVesuVIP Road
📞 Call Sir💬 WhatsApp Sir