1. What is a row-level trigger?
A row-level trigger executes once for each row affected by an INSERT, UPDATE, or DELETE.
It is created using:
FOR EACH ROW
Example
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
INSERT INTO employee_audit (
employee_id,
old_salary,
new_salary,
changed_on
)
VALUES (
:OLD.employee_id,
:OLD.salary,
:NEW.salary,
SYSDATE
);
END;
/
If an UPDATE changes 10 employees, this trigger fires 10 times.
Frequently Asked Questions
2. What is the difference between statement-level and row-level triggers?
| Feature | Statement-level | Row-level |
|---|---|---|
FOR EACH ROW |
No | Yes |
| Fires | Once per SQL statement | Once per affected row |
:OLD, :NEW |
Not available | Available |
| Example | Update 100 rows → 1 firing | Update 100 rows → 100 firings |
Statement-level
CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
BEGIN
DBMS_OUTPUT.PUT_LINE('Update statement executed');
END;
/
Row-level
CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE('One employee updated');
END;
/
3. What are :OLD and :NEW?
They represent the values of a row before and after the DML operation.
INSERT → :NEW available
UPDATE → :OLD and :NEW available
DELETE → :OLD available
Example:
CREATE OR REPLACE TRIGGER trg_salary
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE(
'Old Salary = ' || :OLD.salary
);
DBMS_OUTPUT.PUT_LINE(
'New Salary = ' || :NEW.salary
);
END;
/
4. Can we modify :NEW?
Yes, in a BEFORE row-level trigger.
This is commonly used for defaulting or transforming values.
CREATE OR REPLACE TRIGGER trg_emp_upper
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
:NEW.first_name := UPPER(:NEW.first_name);
:NEW.last_name := UPPER(:NEW.last_name);
END;
/
If you execute:
INSERT INTO employees
(employee_id, first_name, last_name)
VALUES (101, 'john', 'smith');
The stored values become:
JOHN
SMITH
Important:
You cannot modify :OLD.
:OLD.salary := 50000; -- ❌ Invalid
5. Can we use :NEW in a DELETE trigger?
No.
For DELETE:
:OLD → available
:NEW → not available
Example:
CREATE OR REPLACE TRIGGER trg_emp_delete
BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE(
'Deleting employee: ' || :OLD.employee_id
);
END;
/
6. Can we use :OLD during INSERT?
No.
For INSERT:
:NEW → available
:OLD → not available
Example:
CREATE OR REPLACE TRIGGER trg_emp_insert
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE(
'New employee: ' || :NEW.employee_id
);
END;
/
7. What are BEFORE and AFTER row-level triggers?
BEFORE trigger
Runs before the row is inserted/updated/deleted.
Useful for:
- Validation
- Modifying
:NEW - Setting default values
CREATE OR REPLACE TRIGGER trg_validate_salary
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary < 0 THEN
RAISE_APPLICATION_ERROR(
-20001,
'Salary cannot be negative'
);
END IF;
END;
/
AFTER trigger
Runs after the row operation.
Useful for:
- Auditing
- Logging
- Recording changes
CREATE OR REPLACE TRIGGER trg_salary_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO salary_audit
VALUES (
:OLD.employee_id,
:OLD.salary,
:NEW.salary,
SYSDATE
);
END;
/
8. What does UPDATE OF salary mean?
It means the trigger fires only when the salary column is included in the UPDATE statement.
CREATE OR REPLACE TRIGGER trg_salary
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE('Salary update detected');
END;
/
This fires for:
UPDATE employees
SET salary = 60000
WHERE employee_id = 101;
But it doesn't fire for:
UPDATE employees
SET department_id = 20
WHERE employee_id = 101;
UPDATE OF salary means the column is referenced in the UPDATE statement. It doesn't necessarily mean the salary value actually changed.
For example:
UPDATE employees
SET salary = salary
WHERE employee_id = 101;
The trigger can still fire.
9. How do I check whether a value actually changed?
Use :OLD and :NEW.
CREATE OR REPLACE TRIGGER trg_salary_change
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
IF :OLD.salary != :NEW.salary THEN
DBMS_OUTPUT.PUT_LINE('Salary actually changed');
END IF;
END;
/
But there's a NULL issue with !=.
A safer comparison is:
IF (:OLD.salary != :NEW.salary)
OR (:OLD.salary IS NULL AND :NEW.salary IS NOT NULL)
OR (:OLD.salary IS NOT NULL AND :NEW.salary IS NULL)
THEN
DBMS_OUTPUT.PUT_LINE('Salary changed');
END IF;
10. How do I prevent an employee's salary from decreasing?
A row-level BEFORE UPDATE trigger is a common example.
CREATE OR REPLACE TRIGGER trg_no_salary_decrease
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary < :OLD.salary THEN
RAISE_APPLICATION_ERROR(
-20002,
'Salary cannot be decreased'
);
END IF;
END;
/
Now:
UPDATE employees
SET salary = 40000
WHERE employee_id = 101;
will fail if the old salary was 50000.
11. How do I automatically set created_date?
CREATE OR REPLACE TRIGGER trg_emp_created
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
:NEW.created_date := SYSDATE;
END;
/
Now:
INSERT INTO employees(employee_id, first_name)
VALUES (101, 'John');
automatically gets the current date.
Better modern alternative
If you're designing a new table, a column DEFAULT is often preferable:
created_date DATE DEFAULT SYSDATE
Use triggers when the logic genuinely requires procedural behavior.
12. How do I maintain updated_date?
CREATE OR REPLACE TRIGGER trg_emp_updated
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
:NEW.updated_date := SYSDATE;
END;
/
Every updated row gets a new timestamp.
13. Can a trigger call a procedure?
Yes.
CREATE OR REPLACE TRIGGER trg_emp
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
audit_employee(:NEW.employee_id);
END;
/
Where:
CREATE OR REPLACE PROCEDURE audit_employee(
p_employee_id NUMBER
)
IS
BEGIN
INSERT INTO employee_log(employee_id, log_date)
VALUES (p_employee_id, SYSDATE);
END;
/
This is useful when trigger logic becomes large, although you should avoid putting excessive business logic into triggers.
14. What is a mutating-table error?
One of the most important row-trigger interview questions.
Suppose you have:
CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
DECLARE
v_count NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM employees;
END;
/
This can cause:
ORA-04091: table EMPLOYEES is mutating,
trigger/function may not see it
Why?
A row-level trigger is executing while Oracle is modifying the EMPLOYEES table.
Trying to query the same table from that row-level trigger can cause the mutating-table problem.
15. How do we solve a mutating-table problem?
Modern Oracle versions provide several approaches, including compound triggers.
Example:
CREATE OR REPLACE TRIGGER trg_emp
FOR UPDATE ON employees
COMPOUND TRIGGER
AFTER EACH ROW IS
BEGIN
-- collect row information
NULL;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
-- safely query EMPLOYEES here
NULL;
END AFTER STATEMENT;
END;
/
The important idea is:
Row section
↓
collect information
Statement section
↓
query/process the table
This separates row-level collection from statement-level processing.
16. What happens if one row fails in a row-level trigger?
Suppose:
UPDATE employees
SET salary = salary + 1000;
affects 100 rows.
If your trigger raises an exception for one of those rows and the exception propagates, the SQL statement fails, and Oracle normally rolls back the statement's changes.
Example:
CREATE OR REPLACE TRIGGER trg_test
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary > 100000 THEN
RAISE_APPLICATION_ERROR(
-20003,
'Salary too high'
);
END IF;
END;
/
If the update tries to create an invalid row, the statement fails rather than simply skipping that row.
17. Can a trigger commit?
Normally, no.
You cannot do:
CREATE OR REPLACE TRIGGER trg_test
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
COMMIT; -- ❌ ORA-04092
END;
/
A trigger executes as part of the transaction that caused it.
18. Can a trigger perform DML on another table?
Yes.
For example:
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
INSERT INTO employee_deleted_log
(
employee_id,
deleted_date
)
VALUES
(
:OLD.employee_id,
SYSDATE
);
END;
/
This is a common auditing pattern.
19. Can a row-level trigger be used for auditing?
Yes.
Example:
CREATE TABLE emp_audit (
employee_id NUMBER,
old_salary NUMBER,
new_salary NUMBER,
changed_by VARCHAR2(100),
changed_on DATE
);
Trigger:
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO emp_audit
(
employee_id,
old_salary,
new_salary,
changed_by,
changed_on
)
VALUES
(
:OLD.employee_id,
:OLD.salary,
:NEW.salary,
USER,
SYSDATE
);
END;
/
20. What is the difference between :NEW and NEW?
Inside PL/SQL trigger code, you normally use:
:NEW.column_name
:OLD.column_name
The colon indicates a correlation variable.
Example:
IF :NEW.salary > 100000 THEN
...
END IF;
21. Can we use WHEN with a row-level trigger?
Yes.
For example:
CREATE OR REPLACE TRIGGER trg_high_salary
AFTER UPDATE ON employees
FOR EACH ROW
WHEN (NEW.salary > 100000)
BEGIN
INSERT INTO high_salary_log(employee_id)
VALUES (:NEW.employee_id);
END;
/
A useful distinction:
Inside the WHEN clause, you generally reference the correlation name without the colon:
WHEN (NEW.salary > 100000)
Inside PL/SQL:
:NEW.salary
22. What are the valid timing/event combinations?
Common combinations are:
BEFORE INSERT
BEFORE UPDATE
BEFORE DELETE
AFTER INSERT
AFTER UPDATE
AFTER DELETE
You can combine events:
BEFORE INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
You can also restrict an update trigger:
BEFORE UPDATE OF salary, department_id ON employees
FOR EACH ROW
23. Can one trigger handle INSERT, UPDATE and DELETE?
Yes.
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
BEGIN
IF INSERTING THEN
DBMS_OUTPUT.PUT_LINE('Inserted');
ELSIF UPDATING THEN
DBMS_OUTPUT.PUT_LINE('Updated');
ELSIF DELETING THEN
DBMS_OUTPUT.PUT_LINE('Deleted');
END IF;
END;
/
Oracle provides:
INSERTING
UPDATING
DELETING
These are extremely useful in multi-event triggers.
24. What is a good interview example?
Requirement
Solution
CREATE OR REPLACE TRIGGER trg_salary_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
IF (:OLD.salary != :NEW.salary)
OR (:OLD.salary IS NULL AND :NEW.salary IS NOT NULL)
OR (:OLD.salary IS NOT NULL AND :NEW.salary IS NULL)
THEN
INSERT INTO salary_audit
(
employee_id,
old_salary,
new_salary,
changed_by,
changed_on
)
VALUES
(
:OLD.employee_id,
:OLD.salary,
:NEW.salary,
SYS_CONTEXT('USERENV', 'SESSION_USER'),
SYSTIMESTAMP
);
END IF;
END;
/
This demonstrates several important concepts:
- Row-level trigger
AFTER UPDATEUPDATE OF:OLD:NEW- NULL-safe comparison
- Audit logging
- Current database user
- Timestamp
⭐ Quick interview cheat sheet
Remember this table:
| Operation | :OLD |
:NEW |
|---|---|---|
| INSERT | ❌ | ✅ |
| UPDATE | ✅ | ✅ |
| DELETE | ✅ | ❌ |
FOR EACH ROW
↓
Row-level trigger
No FOR EACH ROW
↓
Statement-level trigger
BEFORE → validate/modify :NEW
AFTER → audit/log after the operation
:OLD → previous value
:NEW → new value
INSERTING → INSERT event
UPDATING → UPDATE event
DELETING → DELETE event
:OLD/:NEW edge cases.
No comments:
Post a Comment