Oracle Compound Trigger – Complete Notes with Examples

Oracle Compound Trigger – Complete Notes with Examples

A Compound Trigger in Oracle is a special type of PL/SQL trigger that allows you to combine multiple trigger timing points into a single trigger.

It can execute code at different stages of a DML operation (INSERT, UPDATE, DELETE) on a table.

The biggest advantage of a compound trigger is that it allows you to share common variables and data between different timing sections of the same trigger.

It is especially useful for:

  • Avoiding mutating table errors.
  • Collecting multiple row changes and processing them together.
  • Reducing repeated SQL statements.
  • Improving performance during bulk DML operations.
  • Maintaining audit information.

Let's understand it step by step with practical examples.

1. What is a Compound Trigger?

Normally, Oracle supports different types of triggers:

  1. BEFORE STATEMENT
  2. BEFORE EACH ROW
  3. AFTER EACH ROW
  4. AFTER STATEMENT

Traditionally, we create separate triggers for these timing points.

But a compound trigger allows us to write all these sections inside a single trigger.

Syntax:

CREATE OR REPLACE TRIGGER trigger_name
FOR INSERT OR UPDATE OR DELETE
ON table_name
COMPOUND TRIGGER

    -- Declaration section
    -- Common variables

    BEFORE STATEMENT IS
    BEGIN
        -- Executes before DML statement
    END BEFORE STATEMENT;

    BEFORE EACH ROW IS
    BEGIN
        -- Executes before each affected row
    END BEFORE EACH ROW;

    AFTER EACH ROW IS
    BEGIN
        -- Executes after each affected row
    END AFTER EACH ROW;

    AFTER STATEMENT IS
    BEGIN
        -- Executes after all affected rows
    END AFTER STATEMENT;

END trigger_name;
/
Important: All four timing sections are optional. You can use only the sections required for your business logic.

2. Four Timing Sections in a Compound Trigger

Let's understand each timing section.

Compound Trigger Execution Flow

  1. BEFORE STATEMENT: Executes once before the DML statement starts processing rows.
  2. BEFORE EACH ROW: Executes before each affected row is modified.
  3. DML Operation on Row: Oracle performs INSERT, UPDATE or DELETE.
  4. AFTER EACH ROW: Executes after each affected row is modified.
  5. AFTER STATEMENT: Executes once after all affected rows are processed.

A. BEFORE STATEMENT

  • Executes only once for the entire DML statement.
  • Executes before Oracle starts processing individual rows.
  • Useful for initializing variables and collections.

Example:

BEFORE STATEMENT IS
BEGIN
    DBMS_OUTPUT.PUT_LINE('Before statement');
END BEFORE STATEMENT;

B. BEFORE EACH ROW

  • Executes once for every affected row.
  • Executes before the actual row modification.
  • Useful for validating data and assigning values to :NEW.

Example:

BEFORE EACH ROW IS
BEGIN
    :NEW.salary := NVL(:NEW.salary, 0);
END BEFORE EACH ROW;

C. AFTER EACH ROW

  • Executes once for every affected row.
  • Executes after the row modification.
  • Useful for collecting old and new values.

Example:

AFTER EACH ROW IS
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Employee salary updated'
    );
END AFTER EACH ROW;

D. AFTER STATEMENT

  • Executes only once after all affected rows have been processed.
  • Useful for performing summary operations and bulk processing.

Example:

AFTER STATEMENT IS
BEGIN
    DBMS_OUTPUT.PUT_LINE('After statement');
END AFTER STATEMENT;

3. First Practical Example – Display Trigger Execution Order

Let's create an employee table.

CREATE TABLE employees (
    emp_id NUMBER,
    emp_name VARCHAR2(50),
    salary NUMBER
);

Insert sample records:

INSERT INTO employees VALUES (1, 'John', 50000);
INSERT INTO employees VALUES (2, 'David', 40000);
INSERT INTO employees VALUES (3, 'Smith', 60000);

COMMIT;

Now create a compound trigger.

CREATE OR REPLACE TRIGGER emp_comp_trg
FOR UPDATE ON employees
COMPOUND TRIGGER

    BEFORE STATEMENT IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE(
            '1. BEFORE STATEMENT'
        );
    END BEFORE STATEMENT;

    BEFORE EACH ROW IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE(
            '2. BEFORE EACH ROW - ' || :OLD.emp_name
        );
    END BEFORE EACH ROW;

    AFTER EACH ROW IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE(
            '3. AFTER EACH ROW - ' || :OLD.emp_name
        );
    END AFTER EACH ROW;

    AFTER STATEMENT IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE(
            '4. AFTER STATEMENT'
        );
    END AFTER STATEMENT;

END emp_comp_trg;
/

Execute:

SET SERVEROUTPUT ON;

UPDATE employees
SET salary = salary + 1000;

Output:

1. BEFORE STATEMENT

2. BEFORE EACH ROW - John
3. AFTER EACH ROW - John

2. BEFORE EACH ROW - David
3. AFTER EACH ROW - David

2. BEFORE EACH ROW - Smith
3. AFTER EACH ROW - Smith

4. AFTER STATEMENT

Important Observations

For three updated rows:

Timing SectionNumber of Executions
BEFORE STATEMENT1
BEFORE EACH ROW3
AFTER EACH ROW3
AFTER STATEMENT1
Interview Point: Statement-level sections execute once per DML statement, whereas row-level sections execute once per affected row.

4. Most Important Example – Avoid Mutating Table Error

This is one of the most important interview topics related to compound triggers.

What is a Mutating Table Error?

A mutating table error occurs when a row-level trigger tries to query or modify the same table that is currently being modified by the triggering DML statement.

Oracle generally raises:

ORA-04091: table is mutating,
trigger/function may not see it

Practical Scenario

Suppose we have an EMPLOYEES table.

CREATE TABLE employees (
    emp_id NUMBER,
    emp_name VARCHAR2(50),
    department_id NUMBER,
    salary NUMBER
);

Business Requirement:

Whenever an employee's salary is updated, we want to check the total salary of that employee's department.

  • IT department has three employees.
  • Their total salary is ₹150,000.
  • When one employee's salary changes, we want to calculate the department's new total salary.

Step 1: Create a Normal Trigger

CREATE OR REPLACE TRIGGER emp_salary_trg
AFTER UPDATE OF salary
ON employees
FOR EACH ROW

DECLARE
    v_total_salary NUMBER;

BEGIN

    SELECT SUM(salary)
    INTO v_total_salary
    FROM employees
    WHERE department_id = :NEW.department_id;

    DBMS_OUTPUT.PUT_LINE(
        'Total salary: ' || v_total_salary
    );

END;
/

Step 2: Execute UPDATE

UPDATE employees
SET salary = salary + 5000
WHERE emp_id = 101;

Oracle may return:

ORA-04091: table EMPLOYEES is mutating

Why Does This Happen?

  1. UPDATE is modifying the EMPLOYEES table.
  2. The AFTER EACH ROW trigger executes.
  3. The trigger tries to SELECT from EMPLOYEES.
  4. Oracle prevents this row-level access to the mutating table.

Step 3: Solve Using Compound Trigger

We collect the department IDs during row-level processing.

Then we query the EMPLOYEES table in the AFTER STATEMENT section.

At that time, the row-level processing is complete, so the table is no longer mutating for this statement.

CREATE OR REPLACE TRIGGER emp_salary_comp_trg
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER

    TYPE dept_type IS TABLE OF
        employees.department_id%TYPE
        INDEX BY PLS_INTEGER;

    v_dept_ids dept_type;
    v_count NUMBER := 0;

    AFTER EACH ROW IS
    BEGIN

        v_count := v_count + 1;

        v_dept_ids(v_count) :=
            :NEW.department_id;

    END AFTER EACH ROW;


    AFTER STATEMENT IS

        v_total_salary NUMBER;

    BEGIN

        FOR i IN 1 .. v_count LOOP

            SELECT SUM(salary)
            INTO v_total_salary
            FROM employees
            WHERE department_id = v_dept_ids(i);

            DBMS_OUTPUT.PUT_LINE(
                'Department: ' ||
                v_dept_ids(i) ||
                ' Total Salary: ' ||
                v_total_salary
            );

        END LOOP;

    END AFTER STATEMENT;

END emp_salary_comp_trg;
/

Now execute:

UPDATE employees
SET salary = salary + 5000
WHERE department_id = 10;

The trigger collects department IDs while processing each row.

After all rows are updated, it calculates the department salary.

Important Interview Concept: The main solution is to collect information in the row-level section and query the mutating table in the AFTER STATEMENT section.
Note: The example above is for understanding. In production, you should also eliminate duplicate department IDs to avoid calculating the same department total multiple times.

5. Compound Trigger with Shared Variables

One of the biggest advantages of a compound trigger is sharing variables between timing sections.

In a normal trigger, separate triggers have separate declaration sections.

In a compound trigger, common variables are declared once and shared across all timing sections.

Example

Requirement: Count how many employee records are updated by a single UPDATE statement.

CREATE OR REPLACE TRIGGER emp_count_trg
FOR UPDATE ON employees
COMPOUND TRIGGER

    v_count NUMBER := 0;

    AFTER EACH ROW IS
    BEGIN

        v_count := v_count + 1;

    END AFTER EACH ROW;


    AFTER STATEMENT IS
    BEGIN

        DBMS_OUTPUT.PUT_LINE(
            'Total updated employees: ' || v_count
        );

    END AFTER STATEMENT;

END emp_count_trg;
/

Execute:

UPDATE employees
SET salary = salary + 1000;

Suppose there are 5 employee records.

Output:

Total updated employees: 5

How Does It Work?

  1. v_count is initialized once for the triggering statement.
  2. AFTER EACH ROW increments the variable.
  3. After all rows are processed, AFTER STATEMENT reads the same variable.
  4. It displays the total number of updated records.

This is called sharing common state between timing sections.

6. Compound Trigger for Audit Table

Another real-time use case is maintaining an audit table.

Requirement: Whenever an employee's salary changes, store:

  • Employee ID
  • Old salary
  • New salary
  • Change date

Step 1: Create Employee Table

CREATE TABLE employees (
    emp_id NUMBER,
    emp_name VARCHAR2(50),
    salary NUMBER
);

Step 2: Create Audit Table

CREATE TABLE emp_salary_audit (
    emp_id NUMBER,
    old_salary NUMBER,
    new_salary NUMBER,
    changed_date DATE
);

Step 3: Create Compound Trigger

CREATE OR REPLACE TRIGGER emp_audit_comp_trg
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER

    TYPE audit_record IS RECORD (
        emp_id NUMBER,
        old_salary NUMBER,
        new_salary NUMBER
    );

    TYPE audit_table IS TABLE OF audit_record
        INDEX BY PLS_INTEGER;

    v_audit audit_table;

    v_count NUMBER := 0;


    AFTER EACH ROW IS
    BEGIN

        v_count := v_count + 1;

        v_audit(v_count).emp_id := :NEW.emp_id;

        v_audit(v_count).old_salary := :OLD.salary;

        v_audit(v_count).new_salary := :NEW.salary;

    END AFTER EACH ROW;


    AFTER STATEMENT IS
    BEGIN

        FORALL i IN 1 .. v_count

            INSERT INTO emp_salary_audit
            VALUES (
                v_audit(i).emp_id,
                v_audit(i).old_salary,
                v_audit(i).new_salary,
                SYSDATE
            );

    END AFTER STATEMENT;

END emp_audit_comp_trg;
/

Step 4: Execute UPDATE

UPDATE employees
SET salary = salary + 5000;

Step 5: Check Audit Table

SELECT *
FROM emp_salary_audit;

Sample Output:

EMP_ID OLD_SALARY NEW_SALARY
1015000055000
1024000045000
1036000065000

Why Use FORALL?

FORALL performs bulk DML operations using collection elements.

Instead of executing an individual INSERT for every employee in a PL/SQL loop, it sends the inserts in bulk.

This can reduce PL/SQL-to-SQL context switching and improve performance.

Important: The audit records are part of the same transaction as the employee update. If the transaction is rolled back, the audit inserts are also rolled back.

7. Compound Trigger with INSERT, UPDATE and DELETE

A compound trigger can support multiple DML operations.

Example:

CREATE OR REPLACE TRIGGER emp_all_dml_trg
FOR INSERT OR UPDATE OR DELETE
ON employees
COMPOUND TRIGGER

    BEFORE EACH ROW IS
    BEGIN

        IF INSERTING THEN

            DBMS_OUTPUT.PUT_LINE(
                'Employee inserted'
            );

        ELSIF UPDATING THEN

            DBMS_OUTPUT.PUT_LINE(
                'Employee updated'
            );

        ELSIF DELETING THEN

            DBMS_OUTPUT.PUT_LINE(
                'Employee deleted'
            );

        END IF;

    END BEFORE EACH ROW;


    AFTER STATEMENT IS
    BEGIN

        DBMS_OUTPUT.PUT_LINE(
            'DML statement completed'
        );

    END AFTER STATEMENT;

END emp_all_dml_trg;
/

Here:

  • INSERTING identifies INSERT operations.
  • UPDATING identifies UPDATE operations.
  • DELETING identifies DELETE operations.

These are conditional predicates available in triggers.

8. Compound Trigger vs Normal Trigger

This is a frequently asked interview question.

Feature Normal Trigger Compound Trigger
Timing sections One timing point per trigger Multiple timing points
Shared variables Not shared across separate triggers Shared across timing sections
Mutating table solution More difficult Easier to implement
Bulk processing Possible with additional design Convenient with collections and FORALL
Code organization Multiple triggers may be required Related logic in one trigger
Performance Depends on implementation Can reduce repeated operations
Important: Compound triggers do not automatically make every operation faster. Their performance benefits depend on how you use collections, bulk processing and SQL operations.

9. Important Rules and Restrictions

Remember these Oracle compound trigger rules:

  1. A compound trigger must be created on a table or editioning view.
  2. It supports INSERT, UPDATE and DELETE events.
  3. It can contain up to four timing sections.
  4. All timing sections share the common declaration section.
  5. Variables declared in the common declaration section exist for the duration of the triggering statement.
  6. A compound trigger cannot be autonomous.
  7. You cannot use :OLD and :NEW in statement-level sections.
  8. You can use :OLD and :NEW in row-level sections.
  9. A compound trigger does not have a separate DECLARE keyword for its common declaration section.
  10. If the triggering statement fails and rolls back, the compound trigger's database changes are also rolled back.

10. Important Difference: Compound Trigger vs Statement-Level Trigger

Feature Statement-Level Trigger Compound Trigger
Execution Once per statement Multiple timing points
Row processing No row-level access Can process individual rows
Shared collection Not applicable across separate triggers Yes
AFTER STATEMENT Yes Yes
BEFORE EACH ROW No Yes

A compound trigger is not a replacement for every normal trigger. Use it when multiple timing points or shared state are useful.

11. Frequently Asked Oracle Compound Trigger Interview Questions

Q1. What is a compound trigger?

A compound trigger is a PL/SQL trigger that combines multiple timing sections into one trigger and allows common variables to be shared across them.

Q2. What are the four timing sections?

  • BEFORE STATEMENT
  • BEFORE EACH ROW
  • AFTER EACH ROW
  • AFTER STATEMENT

Q3. Can we use all four sections in one compound trigger?

Yes. All four sections are optional, so you can use any combination.

Q4. What is the biggest advantage of a compound trigger?

Sharing variables between timing sections and avoiding mutating table errors by moving table queries to the AFTER STATEMENT section.

Q5. Can we use COMMIT inside a compound trigger?

No. A normal trigger cannot execute COMMIT or ROLLBACK directly.

Q6. Can we use autonomous transactions?

A compound trigger itself cannot be autonomous. Its individual timing sections cannot be declared autonomous either.

Q7. Can we use :OLD and :NEW in BEFORE STATEMENT?

No. They are available in row-level timing sections.

Q8. Can we use collections in compound triggers?

Yes. Collections are commonly used to collect row information and process it in AFTER STATEMENT.

Q9. Can a compound trigger prevent mutating table errors?

It can help avoid them by collecting row information and accessing the affected table after row-level processing is complete. However, it does not automatically eliminate every possible mutating-table situation.

Q10. What happens if an UPDATE statement updates 100 rows?

  • BEFORE STATEMENT executes once.
  • BEFORE EACH ROW executes 100 times.
  • AFTER EACH ROW executes 100 times.
  • AFTER STATEMENT executes once.

12. Quick Revision Notes

Topic Remember
Compound triggerMultiple timing sections in one trigger
Main advantageShared variables
Mutating table solutionCollect in row section, query in AFTER STATEMENT
BEFORE STATEMENTExecutes once
BEFORE EACH ROWExecutes for every affected row
AFTER EACH ROWExecutes for every affected row
AFTER STATEMENTExecutes once
CollectionsStore row-level information
FORALLBulk DML processing
COMMITNot allowed directly
Autonomous transactionNot allowed for compound trigger

13. Practice Questions

Try these questions to check your understanding of Oracle Compound Triggers.

Oracle Compound Trigger Quiz

1. If an UPDATE statement modifies 10 rows, how many times does AFTER STATEMENT execute?

A. 10 times
B. Once
C. 11 times
D. Zero

2. Where should you query the same mutating table after collecting row information?

A. BEFORE EACH ROW
B. AFTER EACH ROW
C. AFTER STATEMENT
D. BEFORE STATEMENT

3. Which section is suitable for collecting :OLD and :NEW values?

A. BEFORE STATEMENT
B. AFTER EACH ROW
C. AFTER STATEMENT
D. Declaration section

4. Can variables be shared between sections of a compound trigger?

A. Yes
B. No
C. Only global variables
D. Only package variables

5. Can we execute COMMIT directly inside a compound trigger?

A. Yes
B. Only in AFTER STATEMENT
C. No
D. Only with FORALL

Quiz Answers

Click here to view the correct answers
  1. B. Once
  2. C. AFTER STATEMENT
  3. B. AFTER EACH ROW
  4. A. Yes
  5. C. No

Final Interview Tip

If an interviewer asks, "How do you solve ORA-04091 using a compound trigger?", explain this exact approach:

  1. Collect required row values in AFTER EACH ROW.
  2. Store them in a collection declared in the common declaration section.
  3. Access the mutating table in AFTER STATEMENT.
  4. Perform the required calculations or DML operations after all rows have been processed.

This is one of the most important practical uses of Oracle compound triggers.

Oracle FORCE View FAQs with Examples

An Oracle FORCE view is a view that can be created even when the underlying table or other referenced objects do not currently exist.

The key syntax is:

CREATE FORCE VIEW view_name AS
SELECT ...
FROM ...;

The opposite is NOFORCE, which is the normal/default behavior.

1. What is a FORCE view?

A FORCE view tells Oracle:

"Create the view even if the referenced objects are currently missing or invalid."

Example:

CREATE FORCE VIEW employee_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees;

If EMPLOYEES does not exist, Oracle can still create the view, but the view will be INVALID.

CREATE FORCE VIEW
        |
        ↓
Referenced object exists?
        |
   +----+----+
   |         |
  YES        NO
   |         |
   ↓         ↓
 VALID     INVALID
 VIEW       VIEW

2. What is the syntax of a FORCE view?

CREATE FORCE VIEW view_name AS
SELECT ...
FROM ...;

Example:

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

You can also use:

CREATE OR REPLACE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

3. What happens if the underlying table does not exist?

Consider:

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

If EMPLOYEES doesn't exist, Oracle creates the view but marks it invalid.

You can check:

SELECT object_name,
       object_type,
       status
FROM user_objects
WHERE object_name = 'EMP_VIEW';

Possible result:

OBJECT_NAME OBJECT_TYPE STATUS
EMP_VIEW VIEW INVALID

4. Can we query an invalid FORCE view?

No.

Suppose:

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

but EMPLOYEES doesn't exist.

Trying:

SELECT *
FROM emp_view;

will fail because Oracle cannot execute the view until its dependencies are valid.

The important distinction is:

FORCE view
    ↓
Can CREATE the view
    ↓
Even if dependency is missing
    ↓
But cannot successfully SELECT
    ↓
Until dependencies are valid

5. Why would we create an invalid view?

This is useful in development and deployment scenarios where objects are created in a particular order.

For example:

Step 1
Create VIEW
    ↓
Step 2
Create TABLE
    ↓
Step 3
Fix/compile VIEW
    ↓
Step 4
Use VIEW

This can be useful when deploying a large application where dependencies are created separately.

6. What is the difference between FORCE and NOFORCE?

This is one of the most common interview questions.

FORCE

CREATE FORCE VIEW emp_view AS
SELECT employee_id
FROM employees;

Oracle attempts to create the view even if EMPLOYEES doesn't exist.

NOFORCE

CREATE NOFORCE VIEW emp_view AS
SELECT employee_id
FROM employees;

The referenced object must exist and be suitable for creating the view.

Comparison:

Feature FORCE NOFORCE
Referenced object exists ✅ ✅
Referenced object missing View can be created ❌
View may initially be INVALID ✅ Normally no
Default behavior ❌ ✅
Useful for deployment ordering ✅ Less useful

7. What is the default: FORCE or NOFORCE?

NOFORCE is the default.

So:

CREATE VIEW emp_view AS
SELECT employee_id
FROM employees;

behaves like:

CREATE NOFORCE VIEW emp_view AS
SELECT employee_id
FROM employees;

You don't normally need to explicitly write NOFORCE.

8. Can FORCE be used with CREATE OR REPLACE?

Yes.

Example:

CREATE OR REPLACE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name,
       salary
FROM employees;

This is useful when you want to replace an existing view while allowing the view to be created even if dependencies aren't currently valid.

9. Can FORCE create a view when the table does not exist?

Yes.

Example:

CREATE FORCE VIEW future_employee_view AS
SELECT employee_id,
       first_name,
       salary
FROM future_employees;

If FUTURE_EMPLOYEES doesn't exist, the view can still be created.

Check:

SELECT object_name,
       status
FROM user_objects
WHERE object_name = 'FUTURE_EMPLOYEE_VIEW';

You may see:

FUTURE_EMPLOYEE_VIEW    INVALID

10. What happens when the missing table is created later?

Suppose you first create:

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name,
       salary
FROM employees;

but EMPLOYEES doesn't exist.

Then later:

CREATE TABLE employees (
    employee_id NUMBER,
    first_name  VARCHAR2(100),
    salary      NUMBER
);

The view's dependency now exists.

You can explicitly recompile the view:

ALTER VIEW emp_view COMPILE;

Then check:

SELECT object_name,
       status
FROM user_objects
WHERE object_name = 'EMP_VIEW';

Expected:

OBJECT_NAME    STATUS
------------   ------
EMP_VIEW       VALID

11. Does FORCE automatically make the view valid?

No.

This is a very important distinction.

FORCE means:

Allow CREATE

It does not mean:

Ignore dependency problems

Example:

CREATE FORCE VIEW emp_view AS
SELECT employee_id
FROM employees;

If EMPLOYEES doesn't exist:

View creation → allowed
View status   → INVALID
Query         → fails until dependency is resolved

12. Can FORCE be used when a column doesn't exist?

This is an important nuance.

Suppose EMPLOYEES exists but does not have BONUS, and you create:

CREATE FORCE VIEW emp_bonus AS
SELECT employee_id,
       bonus
FROM employees;

The view can be created in an invalid state because the referenced definition cannot be successfully resolved.

Check:

SELECT object_name,
       status
FROM user_objects
WHERE object_name = 'EMP_BONUS';

You may see:

EMP_BONUS    INVALID

The view must be corrected/recompiled after the dependency issue is fixed.

13. Can FORCE create a view based on another missing view?

Yes.

For example:

CREATE FORCE VIEW employee_report AS
SELECT employee_id,
       first_name
FROM employee_details;

If EMPLOYEE_DETAILS doesn't exist, Oracle can create EMPLOYEE_REPORT in an invalid state.

Later:

CREATE VIEW employee_details AS
SELECT employee_id,
       first_name
FROM employees;

Then recompile if necessary:

ALTER VIEW employee_report COMPILE;

14. Can FORCE be useful in deployment scripts?

Yes.

Suppose your application contains:

EMPLOYEES
DEPARTMENTS
EMP_DEPT_VIEW
EMPLOYEE_REPORT

and deployment order is temporarily:

EMP_DEPT_VIEW
      ↓
EMPLOYEES
      ↓
DEPARTMENTS
      ↓
EMPLOYEE_REPORT

A FORCE view can allow the view definition to be created before all dependencies are available.

For example:

CREATE FORCE VIEW emp_dept_view AS
SELECT e.employee_id,
       e.first_name,
       d.department_name
FROM employees e
JOIN departments d
ON e.department_id = d.department_id;

Later, after the tables exist:

ALTER VIEW emp_dept_view COMPILE;

15. How do I check whether a FORCE-created view is valid?

Use USER_OBJECTS.

SELECT object_name,
       object_type,
       status
FROM user_objects
WHERE object_type = 'VIEW';

For one view:

SELECT object_name,
       status
FROM user_objects
WHERE object_type = 'VIEW'
AND object_name = 'EMP_VIEW';

Possible result:

OBJECT_NAME    STATUS
------------   ------
EMP_VIEW       VALID

or:

OBJECT_NAME    STATUS
------------   ------
EMP_VIEW       INVALID

16. How do I find why a FORCE view is invalid?

Use:

SELECT name,
       type,
       line,
       position,
       text
FROM user_errors
WHERE name = 'EMP_VIEW'
ORDER BY sequence;

This can show compilation errors associated with the view.

For example, you might find an error related to:

ORA-00942: table or view does not exist

17. Can I compile a FORCE view?

Yes.

Use:

ALTER VIEW emp_view COMPILE;

Then check:

SELECT object_name,
       status
FROM user_objects
WHERE object_name = 'EMP_VIEW';

If all dependencies are now correct:

EMP_VIEW    VALID

18. What is the difference between FORCE view and ALTER VIEW COMPILE?

They solve different problems.

FORCE

Used when creating the view:

CREATE FORCE VIEW emp_view AS
...

It allows creation despite dependency problems.

ALTER VIEW ... COMPILE

Used to compile/recompile an existing view:

ALTER VIEW emp_view COMPILE;

So:

CREATE FORCE
    ↓
Create the view even if dependencies aren't ready

ALTER VIEW COMPILE
    ↓
Try to compile the existing view

19. Can FORCE be used with a simple view?

Yes.

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name,
       salary
FROM employees;

20. Can FORCE be used with a complex view?

Yes.

Example:

CREATE FORCE VIEW department_report AS
SELECT d.department_id,
       d.department_name,
       COUNT(e.employee_id) AS employee_count,
       AVG(e.salary) AS average_salary
FROM departments d
LEFT JOIN employees e
ON d.department_id = e.department_id
GROUP BY d.department_id,
         d.department_name;

If the referenced objects aren't currently available, the view can be created but may remain invalid.

21. Can FORCE be used with a materialized view?

Be careful here.

FORCE in:

CREATE FORCE VIEW ...

refers to a normal view.

A materialized view has different creation and refresh concepts, such as:

BUILD IMMEDIATE
BUILD DEFERRED
REFRESH FAST
REFRESH COMPLETE
REFRESH FORCE

Do not confuse:

FORCE VIEW

with:

REFRESH FORCE MATERIALIZED VIEW

They are different concepts.

22. FORCE View vs Materialized View REFRESH FORCE

This is a common interview trap.

Normal view:

CREATE FORCE VIEW emp_view AS
SELECT ...
FROM employees;

FORCE controls whether Oracle allows creation when dependencies cannot currently be resolved.

Materialized view:

CREATE MATERIALIZED VIEW emp_mv
REFRESH FORCE
ON DEMAND
AS
SELECT ...
FROM employees;

REFRESH FORCE tells Oracle to choose an appropriate refresh method, typically fast if possible, otherwise complete.

So:

FORCE VIEW
    ↓
View creation behavior

REFRESH FORCE
    ↓
Materialized-view refresh behavior

They are unrelated features.

23. Does FORCE mean the view will always work?

No.

This is probably the most important concept.

For example:

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

If EMPLOYEES does not exist:

CREATE → succeeds
STATUS → INVALID
SELECT → fails

After creating the missing table and successfully compiling:

STATUS → VALID
SELECT → works

Therefore:

FORCE allows creation; it does not guarantee successful execution.

24. Can FORCE hide errors permanently?

No.

It doesn't fix the underlying problem.

For example:

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       unknown_column
FROM employees;

The view may exist, but it remains invalid until the definition/dependency problem is fixed.

You should always check:

SELECT object_name,
       status
FROM user_objects
WHERE object_type = 'VIEW';

and:

SELECT name,
       line,
       position,
       text
FROM user_errors
WHERE name = 'EMP_VIEW';

25. What are the advantages of FORCE views?

Common advantages include:

26. Flexible deployment

Views can be created before all dependencies are available.

27. Dependency-order flexibility

Useful when objects are deployed in different scripts.

28. Development convenience

Developers can define dependent views before completing all underlying objects.

29. Deployment automation

Large database deployments can create object definitions before all dependencies are available.

30. What are the disadvantages of FORCE views?

The main disadvantage is that you can end up with invalid objects.

FORCE VIEW
    ↓
INVALID
    ↓
Application queries it
    ↓
ERROR

Therefore, after deployment you should check:

SELECT object_name,
       object_type,
       status
FROM user_objects
WHERE status = 'INVALID';

This is especially important in production deployments.

31. FORCE vs NOFORCE — Complete Example

Step 1: Assume the table doesn't exist

DROP TABLE employees PURGE;

Step 2: Try NOFORCE

CREATE NOFORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

This fails because the referenced table doesn't exist.

Step 3: Try FORCE

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

The view can be created, but it is invalid.

Check:

SELECT object_name,
       status
FROM user_objects
WHERE object_name = 'EMP_VIEW';

Result:

EMP_VIEW    INVALID

Step 4: Create the table

CREATE TABLE employees (
    employee_id NUMBER,
    first_name  VARCHAR2(100)
);

Step 5: Compile the view

ALTER VIEW emp_view COMPILE;

Step 6: Check status

SELECT object_name,
       status
FROM user_objects
WHERE object_name = 'EMP_VIEW';

Result:

EMP_VIEW    VALID

Now:

SELECT *
FROM emp_view;

can execute successfully.

32. What are the common FORCE-view interview traps?

Trap 1 — Does CREATE FORCE VIEW guarantee a valid view?

❌ No. It can create an INVALID view.

Trap 2 — Can you query an invalid FORCE view successfully?

❌ No. The dependencies must be resolved and the view must compile successfully.

Trap 3 — Is FORCE the default?

❌ No. NOFORCE is the default.

Trap 4 — Can FORCE be used with CREATE OR REPLACE VIEW?

✅ Yes.

CREATE OR REPLACE FORCE VIEW emp_view AS
...

Trap 5 — Does FORCE fix missing tables or columns?

❌ No. It only allows the view definition to be created despite dependency problems.

Trap 6 — How do you make an invalid view valid after fixing dependencies?

Use:

ALTER VIEW emp_view COMPILE;

Trap 7 — Is FORCE VIEW the same as REFRESH FORCE for materialized views?

❌ No. They are completely different concepts.

33. FORCE View Cheat Sheet

CREATE FORCE VIEW
        |
        ↓
Create view definition
        |
   +----+----+
   |         |
Dependencies  Dependencies
exist         missing
   |             |
   ↓             ↓
 VALID         INVALID
   |             |
   ↓             ↓
SELECT       Fix dependency
                 |
                 ↓
        ALTER VIEW COMPILE
                 |
                 ↓
               VALID

Key Commands

Create a FORCE view:

CREATE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

Create or replace:

CREATE OR REPLACE FORCE VIEW emp_view AS
SELECT employee_id,
       first_name
FROM employees;

Compile:

ALTER VIEW emp_view COMPILE;

Check status:

SELECT object_name,
       status
FROM user_objects
WHERE object_type = 'VIEW';

Check errors:

SELECT name,
       line,
       position,
       text
FROM user_errors
WHERE name = 'EMP_VIEW'
ORDER BY sequence;

Most Important Interview Statement

CREATE FORCE VIEW allows Oracle to create a view even when referenced objects cannot currently be resolved. The resulting view may be INVALID and cannot be successfully queried until its dependencies are fixed and the view compiles successfully.

And remember:

NOFORCE
   ↓
Normal/default behavior
   ↓
Dependencies must be valid

FORCE
   ↓
Allow creation despite dependency problems
   ↓
May create INVALID view

ALTER VIEW ... COMPILE
   ↓
Recompile after fixing dependencies

Oracle Read-Only View FAQs with Examples

A read-only view is a view through which users can query data but cannot perform INSERT, UPDATE, or DELETE.

Oracle provides the WITH READ ONLY clause to explicitly make a view read-only.

1. What is a read-only view?

A read-only view allows:

SELECT

but prevents:

INSERT
UPDATE
DELETE

Example

CREATE OR REPLACE VIEW emp_report AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WITH READ ONLY;

Users can query:

SELECT *
FROM emp_report;

But this is not allowed:

UPDATE emp_report
SET salary = 60000
WHERE employee_id = 101;

2. Why do we use a read-only view?

Read-only views are commonly used for:

  • Reporting
  • Data security
  • Data presentation
  • Restricting DML
  • Providing controlled access to data
  • Exposing selected columns to users

For example:

CREATE OR REPLACE VIEW employee_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WITH READ ONLY;

The reporting user can read employee information without being able to modify it through the view.

3. What is the syntax for a read-only view?

CREATE [OR REPLACE] VIEW view_name AS
SELECT ...
FROM ...
[WHERE ...]
WITH READ ONLY;

Example:

CREATE OR REPLACE VIEW dept10_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WHERE department_id = 10
WITH READ ONLY;

4. Can we SELECT from a read-only view?

Yes.

SELECT *
FROM dept10_report;

A read-only view behaves normally for querying.

You can also use:

SELECT employee_id,
       first_name,
       salary
FROM dept10_report
WHERE salary > 50000;

The WITH READ ONLY clause does not prevent queries.

5. Can we INSERT into a read-only view?

No.

Example:

CREATE OR REPLACE VIEW dept10_report AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH READ ONLY;

Attempt:

INSERT INTO dept10_report
(
    employee_id,
    first_name,
    salary,
    department_id
)
VALUES
(
    201,
    'DAVID',
    60000,
    10
);

Oracle rejects the DML because the view is read-only.

You may encounter:

ORA-42399: cannot perform a DML operation on a read-only view

6. Can we UPDATE a read-only view?

No.

Example:

UPDATE dept10_report
SET salary = salary + 1000
WHERE employee_id = 101;

This fails because:

VIEW
  ↓
WITH READ ONLY
  ↓
UPDATE not allowed

7. Can we DELETE from a read-only view?

No.

Example:

DELETE FROM dept10_report
WHERE employee_id = 101;

This is rejected because the view is explicitly read-only.

8. Does WITH READ ONLY affect the underlying table?

No. This is an important point.

Suppose:

CREATE OR REPLACE VIEW emp_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WITH READ ONLY;

You cannot do:

UPDATE emp_report
SET salary = 70000
WHERE employee_id = 101;

But if the user has sufficient privileges on EMPLOYEES, this is still possible:

UPDATE employees
SET salary = 70000
WHERE employee_id = 101;

So:

WITH READ ONLY
      ↓
Restricts DML through the VIEW
      ↓
Does NOT make the underlying TABLE read-only

9. Is every complex view automatically read-only?

Not necessarily.

A complex view may contain:

  • Joins
  • Aggregates
  • GROUP BY
  • DISTINCT
  • Set operators
  • Calculated expressions

Many such views are not directly updatable, but you should not equate complex view with explicitly read-only view.

For example:

CREATE OR REPLACE VIEW dept_summary AS
SELECT department_id,
       COUNT(*) AS employee_count,
       AVG(salary) AS average_salary
FROM employees
GROUP BY department_id;

This is normally not directly updatable because of the aggregation.

But:

WITH READ ONLY

explicitly declares that the view is read-only.

10. What is the difference between a non-updatable view and WITH READ ONLY?

This is an important interview question.

Non-updatable view

A view may naturally be non-updatable because of its definition.

Example:

CREATE OR REPLACE VIEW dept_summary AS
SELECT department_id,
       COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;

The GROUP BY makes normal direct DML inappropriate.

Explicit read-only view

CREATE OR REPLACE VIEW emp_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WITH READ ONLY;

This view might otherwise be updatable, but WITH READ ONLY explicitly prevents DML through it.

Remember:

Non-updatable view
      ↓
DML isn't supported because of view definition/rules

WITH READ ONLY
      ↓
DML is explicitly prohibited

11. Can a simple view be made read-only?

Yes.

Example:

CREATE OR REPLACE VIEW emp_view AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WITH READ ONLY;

Even though this is a simple view and could normally be updatable, WITH READ ONLY prevents DML.

12. Can a complex view be made read-only?

Yes.

Example:

CREATE OR REPLACE VIEW department_report AS
SELECT d.department_id,
       d.department_name,
       COUNT(e.employee_id) AS employee_count,
       AVG(e.salary) AS average_salary
FROM departments d
LEFT JOIN employees e
ON d.department_id = e.department_id
GROUP BY d.department_id,
         d.department_name
WITH READ ONLY;

This is a good reporting view.

Users can:

SELECT *
FROM department_report;

but cannot modify it.

13. Can a read-only view contain a WHERE clause?

Yes.

CREATE OR REPLACE VIEW high_salary_employees AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WHERE salary >= 100000
WITH READ ONLY;

The view returns only employees with:

salary >= 100000

And because of WITH READ ONLY, users cannot modify the data through the view.

14. Can a read-only view contain joins?

Yes.

Example:

CREATE OR REPLACE VIEW emp_dept_report AS
SELECT e.employee_id,
       e.first_name,
       e.salary,
       d.department_name
FROM employees e
JOIN departments d
ON e.department_id = d.department_id
WITH READ ONLY;

This is useful for reporting because users can see information from both tables without being able to modify either through the view.

15. Can a read-only view contain GROUP BY?

Yes.

CREATE OR REPLACE VIEW dept_salary_report AS
SELECT department_id,
       COUNT(*) AS employee_count,
       SUM(salary) AS total_salary,
       AVG(salary) AS average_salary
FROM employees
GROUP BY department_id
WITH READ ONLY;

Query:

SELECT *
FROM dept_salary_report;

Possible result:

DEPARTMENT_ID  EMPLOYEE_COUNT  TOTAL_SALARY  AVERAGE_SALARY
-------------  --------------  ------------  --------------
10             5               350000        70000
20             8               640000        80000
30             4               300000        75000

This is a classic read-only reporting view.

16. Can a read-only view contain aggregate functions?

Yes.

For example:

CREATE OR REPLACE VIEW salary_statistics AS
SELECT COUNT(*) AS employee_count,
       SUM(salary) AS total_salary,
       AVG(salary) AS average_salary,
       MIN(salary) AS minimum_salary,
       MAX(salary) AS maximum_salary
FROM employees
WITH READ ONLY;

This view is designed for reporting.

17. Can we use WITH READ ONLY with WITH CHECK OPTION?

No. They serve opposite purposes.

WITH READ ONLY
      ↓
DML prohibited

WITH CHECK OPTION
      ↓
DML may be allowed
but modified rows must satisfy
the view condition

For example:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

This allows appropriate DML while enforcing the condition.

Whereas:

CREATE OR REPLACE VIEW dept10_report AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH READ ONLY;

allows querying but no DML.

Easy comparison

WITH CHECK OPTION
      ↓
DML allowed
      ↓
Don't violate WHERE condition

WITH READ ONLY
      ↓
DML not allowed

18. Can we use WITH READ ONLY with an INSTEAD OF trigger?

This is an important conceptual question.

An explicitly read-only view cannot be used as a normal DML interface simply by adding an INSTEAD OF trigger.

WITH READ ONLY declares that DML through the view is not allowed.

If you need DML through a complex view, an INSTEAD OF trigger is a common mechanism—but the view should not be explicitly declared WITH READ ONLY for that purpose.

19. Can a read-only view improve security?

Yes, but understand its scope.

Suppose the underlying table contains:

EMPLOYEE_ID
FIRST_NAME
SALARY
BANK_ACCOUNT
PASSWORD_HASH

You may expose only appropriate columns:

CREATE OR REPLACE VIEW employee_public_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WITH READ ONLY;

Then grant access to the view:

GRANT SELECT
ON employee_public_report
TO reporting_user;

The reporting user can query the view:

SELECT *
FROM employee_public_report;

but the view itself does not permit:

INSERT
UPDATE
DELETE

For security-sensitive systems, you should also carefully control direct privileges on the underlying tables.

20. Does WITH READ ONLY hide the underlying table?

No.

WITH READ ONLY does not automatically hide the underlying table.

For example:

CREATE OR REPLACE VIEW employee_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WITH READ ONLY;

The view does not automatically revoke:

SELECT

or DML privileges on:

EMPLOYEES

If a user already has direct privileges on EMPLOYEES, they may still access it directly.

21. Can we grant SELECT on a read-only view?

Yes.

Example:

GRANT SELECT
ON employee_report
TO reporting_user;

Now the user can execute:

SELECT *
FROM employee_report;

but the view itself does not permit:

INSERT
UPDATE
DELETE

22. Can a read-only view be used for reporting?

Yes. This is one of its most common uses.

Example:

CREATE OR REPLACE VIEW monthly_employee_report AS
SELECT department_id,
       COUNT(*) AS employee_count,
       SUM(salary) AS total_salary,
       AVG(salary) AS average_salary
FROM employees
GROUP BY department_id
WITH READ ONLY;

Applications can simply use:

SELECT *
FROM monthly_employee_report;

without giving users a way to modify the report through the view.

23. Does a read-only view store data?

No.

A normal Oracle view stores the view definition, not a physical copy of the result.

Example:

CREATE OR REPLACE VIEW emp_report AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WITH READ ONLY;

Conceptually:

EMPLOYEES
   ↓
VIEW DEFINITION
   ↓
EMP_REPORT
   ↓
SELECT
   ↓
Current data

If the underlying employee salary changes, the next query against the view reflects the current underlying data.

If you need physically stored query results, consider a materialized view.

24. Read-only view vs materialized view

Feature Read-Only View Materialized View
Stores query definition ✅ ✅
Stores query result physically ❌ ✅
SELECT ✅ ✅
DML through view ❌ Different rules
Refresh required ❌ Often ✅
Good for reporting ✅ ✅
WITH READ ONLY ✅ ❌ Different concept

Example read-only view:

CREATE OR REPLACE VIEW dept_report AS
SELECT department_id,
       COUNT(*) AS employee_count
FROM employees
GROUP BY department_id
WITH READ ONLY;

Materialized view:

CREATE MATERIALIZED VIEW dept_report_mv
BUILD IMMEDIATE
REFRESH COMPLETE
ON DEMAND
AS
SELECT department_id,
       COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;

The materialized view stores the result physically and has refresh considerations.

25. How can I check whether a view is read-only?

You can inspect the data dictionary.

For example:

SELECT view_name,
       read_only
FROM user_views
WHERE view_name = 'EMP_REPORT';

The READ_ONLY metadata indicates whether the view was defined as read-only.

You can also inspect the complete view definition:

SELECT text
FROM user_views
WHERE view_name = 'EMP_REPORT';

26. What is a complete read-only reporting example?

Suppose we have:

CREATE TABLE departments (
    department_id   NUMBER PRIMARY KEY,
    department_name VARCHAR2(100)
);

and:

CREATE TABLE employees (
    employee_id   NUMBER PRIMARY KEY,
    first_name    VARCHAR2(100),
    salary        NUMBER,
    department_id NUMBER
);

Create a reporting view:

CREATE OR REPLACE VIEW department_report AS
SELECT d.department_id,
       d.department_name,
       COUNT(e.employee_id) AS employee_count,
       NVL(SUM(e.salary), 0) AS total_salary,
       NVL(AVG(e.salary), 0) AS average_salary
FROM departments d
LEFT JOIN employees e
ON d.department_id = e.department_id
GROUP BY d.department_id,
         d.department_name
WITH READ ONLY;

Query:

SELECT *
FROM department_report;

Possible result:

DEPARTMENT_ID  DEPARTMENT_NAME  EMPLOYEE_COUNT  TOTAL_SALARY  AVERAGE_SALARY
-------------  ---------------  --------------  ------------  --------------
10             IT               5               350000        70000
20             HR               3               210000        70000
30             SALES            8               640000        80000

Users can query the report but cannot execute:

UPDATE department_report ...

or:

DELETE FROM department_report ...

or:

INSERT INTO department_report ...

27. What are the most common read-only-view interview traps?

Trap 1

Can we SELECT from a read-only view?

✅ Yes.

Trap 2

Can we UPDATE a read-only view?

❌ No.

Trap 3

Can we INSERT into a read-only view?

❌ No.

Trap 4

Can we DELETE from a read-only view?

❌ No.

Trap 5

Does WITH READ ONLY make the underlying table read-only?

❌ No.

It only restricts DML through the view.

Trap 6

Does WITH READ ONLY store a copy of the data?

❌ No.

A normal view stores its definition, not its result.

Trap 7

Can a simple view be declared read-only?

✅ Yes.

Trap 8

Can a complex view be declared read-only?

✅ Yes.

This is very common for reporting views.

Trap 9

Does WITH READ ONLY mean the view is automatically secure?

❌ Not by itself.

You still need appropriate privileges on the view and underlying objects.

28. Read-Only View vs WITH CHECK OPTION

Feature WITH READ ONLY WITH CHECK OPTION
SELECT ✅ ✅
INSERT ❌ Possible if otherwise updatable
UPDATE ❌ Possible if otherwise updatable
DELETE ❌ Generally possible if otherwise deletable
Enforces view WHERE condition Not applicable to DML ✅
Makes view read-only ✅ ❌
Makes non-updatable view updatable ❌ ❌

Remember:

WITH READ ONLY
      ↓
NO DML THROUGH VIEW

WITH CHECK OPTION
      ↓
DML MAY BE ALLOWED
      ↓
BUT RESULT MUST SATISFY VIEW CONDITION

29. Read-Only View Cheat Sheet

READ-ONLY VIEW
      |
      ↓
WITH READ ONLY
      |
      +---------+---------+
      |         |         |
    SELECT    INSERT    UPDATE
      |         |         |
      ↓         ❌        ❌
     YES
      |
    DELETE
      |
      ❌

Key points to remember

Question Answer
What is a read-only view? A view that doesn't allow DML through the view
Syntax? WITH READ ONLY
SELECT allowed? ✅
INSERT allowed? ❌
UPDATE allowed? ❌
DELETE allowed? ❌
Underlying table becomes read-only? ❌
Can simple views be read-only? ✅
Can complex views be read-only? ✅
Does it store data? ❌
Good for reporting? ✅
Same as WITH CHECK OPTION? ❌
Can it be queried normally? ✅

Most important interview statement

WITH READ ONLY explicitly prevents INSERT, UPDATE, and DELETE operations through a view while still allowing users to query the view.

The easiest distinction to remember is:

WITH READ ONLY
      ↓
No DML

WITH CHECK OPTION
      ↓
DML allowed when possible
      ↓
Don't violate the view's WHERE condition

Oracle WITH CHECK OPTION View FAQs with Examples

The WITH CHECK OPTION clause is used when creating a view to ensure that rows modified through the view continue to satisfy the view's WHERE condition.

It is especially useful when you want users to insert or update rows only within the subset of data exposed by the view.

1. What is WITH CHECK OPTION in Oracle?

WITH CHECK OPTION tells Oracle:

Any INSERT or UPDATE performed through this view must result in a row that still satisfies the view's WHERE condition.

Example

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

The view shows only:

DEPARTMENT_ID = 10

Therefore, this is allowed:

UPDATE dept10_employees
SET salary = salary + 1000
WHERE employee_id = 101;

But this is rejected:

UPDATE dept10_employees
SET department_id = 20
WHERE employee_id = 101;

Why?

Because after the update, the employee would no longer satisfy:

department_id = 10

2. Why do we use WITH CHECK OPTION?

It prevents users from modifying data through a view in a way that makes the modified row disappear from the view.

Without WITH CHECK OPTION:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10;

A user might execute:

UPDATE dept10_employees
SET department_id = 20
WHERE employee_id = 101;

The update can move the employee out of department 10.

Afterward:

SELECT *
FROM dept10_employees;

will no longer show employee 101.

With WITH CHECK OPTION, Oracle prevents that type of update.

3. What is the basic syntax?

CREATE [OR REPLACE] VIEW view_name AS
SELECT column1,
       column2,
       ...
FROM table_name
WHERE condition
WITH CHECK OPTION;

Example:

CREATE OR REPLACE VIEW high_salary_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE salary >= 50000
WITH CHECK OPTION;

The view allows only rows satisfying:

salary >= 50000

4. What happens without WITH CHECK OPTION?

Consider:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10;

Suppose:

EMPLOYEE_ID  FIRST_NAME  SALARY  DEPARTMENT_ID

101          JOHN        60000   10

Now:

UPDATE dept10_employees
SET department_id = 20
WHERE employee_id = 101;

The update can succeed if the view is otherwise updatable.

But now:

SELECT *
FROM dept10_employees;

doesn't show employee 101.

The row has moved outside the view's condition.

5. What happens with WITH CHECK OPTION?

Create:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

Then:

UPDATE dept10_employees
SET department_id = 20
WHERE employee_id = 101;

Oracle rejects the operation because the resulting row would violate the view condition.

You may encounter an error such as:

ORA-01402: view WITH CHECK OPTION where-clause violation

The important concept is:

View condition
      ↓
UPDATE through view
      ↓
Does resulting row satisfy condition?
      ↓
YES → allowed
NO  → rejected

6. Does WITH CHECK OPTION apply to SELECT?

No.

It affects DML performed through an updatable view, particularly:

INSERT
UPDATE

It does not change the behavior of:

SELECT *
FROM dept10_employees;

The SELECT simply returns rows satisfying the view definition.

7. Does WITH CHECK OPTION affect DELETE?

No.

DELETE removes a row rather than modifying it into a state that violates the view's condition.

For example:

DELETE FROM dept10_employees
WHERE employee_id = 101;

The row is deleted from the underlying table if the view is otherwise deletable.

WITH CHECK OPTION is primarily concerned with ensuring that inserted or updated rows remain visible through the view.

8. Can we use WITH CHECK OPTION with INSERT?

Yes, provided the view is otherwise insertable.

Example:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

This is allowed:

INSERT INTO dept10_employees
(
    employee_id,
    first_name,
    salary,
    department_id
)
VALUES
(
    201,
    'DAVID',
    60000,
    10
);

Because:

department_id = 10
satisfies the view condition.

But:

INSERT INTO dept10_employees
(
    employee_id,
    first_name,
    salary,
    department_id
)
VALUES
(
    202,
    'ROBERT',
    60000,
    20
);

is rejected.

Why?

View condition:
department_id = 10

Inserted value:
department_id = 20

Result:
Condition violated

9. Can WITH CHECK OPTION be used with a salary condition?

Yes.

CREATE OR REPLACE VIEW high_salary_employees AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WHERE salary >= 50000
WITH CHECK OPTION;

This is allowed:

UPDATE high_salary_employees
SET salary = 60000
WHERE employee_id = 101;

But:

UPDATE high_salary_employees
SET salary = 40000
WHERE employee_id = 101;

is rejected because:

40000 < 50000

The resulting row would no longer satisfy the view condition.

10. Can WITH CHECK OPTION have a name?

Yes.

You can specify a constraint name.

Syntax:

WITH CHECK OPTION CONSTRAINT constraint_name

Example:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION CONSTRAINT dept10_check;

Here:

dept10_check

is the name of the CHECK OPTION constraint.

This can make error messages and metadata easier to understand.

11. What is WITH CASCADED CHECK OPTION?

CASCADED means the check is applied to the current view and relevant underlying views in the view hierarchy.

Example:

First view:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

Now create another view on top of it:

CREATE OR REPLACE VIEW high_paid_dept10 AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM dept10_employees
WHERE salary >= 50000
WITH CASCADED CHECK OPTION;

There are now two conditions:

EMPLOYEES
    ↓
DEPT10_EMPLOYEES
department_id = 10
    ↓
HIGH_PAID_DEPT10
salary >= 50000

A modification through HIGH_PAID_DEPT10 must respect the applicable conditions.

12. What is WITH LOCAL CHECK OPTION?

LOCAL means the check is applied to the current view's condition, rather than cascading through all underlying view conditions in the same way as CASCADED.

Example:

CREATE OR REPLACE VIEW high_paid_dept10 AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM dept10_employees
WHERE salary >= 50000
WITH LOCAL CHECK OPTION;

The key interview distinction is:

LOCAL
↓
Checks the current view condition

CASCADED
↓
Checks the current view and applicable
underlying view conditions

13. What is the difference between LOCAL and CASCADED?

Consider:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

Then:

CREATE OR REPLACE VIEW high_paid_dept10 AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM dept10_employees
WHERE salary >= 50000
WITH LOCAL CHECK OPTION;

Conceptually:

EMPLOYEES
   |
   | department_id = 10
   ↓
DEPT10_EMPLOYEES
   |
   | salary >= 50000
   ↓
HIGH_PAID_DEPT10

The distinction is:

Option Meaning
LOCAL Checks the current view's condition
CASCADED Checks the current view and underlying view conditions
WITH CHECK OPTION Oracle's default is effectively CASCADED

For interviews, remember:

LOCAL     → current view
CASCADED  → current + underlying views

14. What is the default if we simply write WITH CHECK OPTION?

If neither LOCAL nor CASCADED is specified:

WITH CHECK OPTION

Oracle treats it as:

WITH CASCADED CHECK OPTION

Conceptually:

WITH CHECK OPTION
is equivalent to:
WITH CASCADED CHECK OPTION

for this purpose.

15. Can WITH CHECK OPTION make a non-updatable view updatable?

No.

This is an important interview trap.

Suppose:

CREATE OR REPLACE VIEW dept_summary AS
SELECT department_id,
       COUNT(*) AS employee_count,
       AVG(salary) AS average_salary
FROM employees
GROUP BY department_id
WITH CHECK OPTION;

Adding:

WITH CHECK OPTION

does not make this aggregate view updatable.

WITH CHECK OPTION controls which modifications are permitted through an already-updatable view.

It does not turn a non-updatable view into an updatable one.

16. Can WITH CHECK OPTION be used with a simple view?

Yes. This is one of the most common uses.

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

This is a straightforward, updatable view with a restriction.

17. Can WITH CHECK OPTION be used with a complex view?

It can be used where the view definition and DML rules permit it, but WITH CHECK OPTION does not overcome the normal restrictions on complex views.

For example, an aggregate view:

CREATE OR REPLACE VIEW dept_summary AS
SELECT department_id,
       COUNT(*) AS employee_count
FROM employees
GROUP BY department_id
WITH CHECK OPTION;

is still not normally updatable.

Remember:

WITH CHECK OPTION
       ↓
Restricts DML through a view

NOT

WITH CHECK OPTION
       ↓
Makes every view updatable

18. Does WITH CHECK OPTION check the original or new values?

For an UPDATE, it effectively checks the resulting row against the view's condition.

Example:

CREATE OR REPLACE VIEW high_salary_employees AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WHERE salary >= 50000
WITH CHECK OPTION;

Suppose:

OLD salary = 60000

Then:

UPDATE high_salary_employees
SET salary = 45000
WHERE employee_id = 101;

The resulting salary is:

45000

which violates:

salary >= 50000

Therefore the update is rejected.

19. What happens if an UPDATE doesn't affect the view condition?

Suppose:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

This is perfectly valid:

UPDATE dept10_employees
SET salary = salary + 5000
WHERE employee_id = 101;

The department remains:

10

Therefore:

department_id = 10
        ↓
condition satisfied
        ↓
UPDATE allowed

20. Can WITH CHECK OPTION protect multiple conditions?

Yes.

Example:

CREATE OR REPLACE VIEW dept10_high_salary AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
AND salary >= 50000
WITH CHECK OPTION;

Now the resulting row must satisfy:

department_id = 10
AND
salary >= 50000

This is allowed:

UPDATE dept10_high_salary
SET salary = 60000
WHERE employee_id = 101;

But this isn't:

UPDATE dept10_high_salary
SET salary = 30000
WHERE employee_id = 101;

because:

salary >= 50000
is false.

Likewise:

UPDATE dept10_high_salary
SET department_id = 20
WHERE employee_id = 101;

violates:

department_id = 10

21. Can WITH CHECK OPTION be used with a date condition?

Yes.

Example:

CREATE OR REPLACE VIEW recent_employees AS
SELECT employee_id,
       first_name,
       hire_date
FROM employees
WHERE hire_date >= DATE '2026-01-01'
WITH CHECK OPTION;

An update that changes hire_date to:

DATE '2025-01-01'

would violate the view condition and therefore be rejected.

22. Can WITH CHECK OPTION be used for security?

Yes, but it should be viewed primarily as a DML consistency mechanism, not as a complete security feature.

For example:

CREATE OR REPLACE VIEW department_10_data AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

This ensures that DML through the view cannot move a row outside department 10.

You can additionally control access:

GRANT SELECT, INSERT, UPDATE
ON department_10_data
TO app_user;

For robust security, Oracle's broader security features may also be appropriate depending on the requirement.

23. Does WITH CHECK OPTION prevent direct table updates?

No.

This is very important.

Suppose:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

The following through the view is restricted:

UPDATE dept10_employees
SET department_id = 20
WHERE employee_id = 101;

But if a user has direct privileges on EMPLOYEES, they could potentially execute:

UPDATE employees
SET department_id = 20
WHERE employee_id = 101;

WITH CHECK OPTION applies to DML through the view. It doesn't impose a database-wide constraint on the underlying table.

24. Is WITH CHECK OPTION a table constraint?

No.

It is a view clause.

Compare:

CHECK constraint
↓
Table-level data integrity

versus:

WITH CHECK OPTION
↓
Controls DML through a view

Example table constraint:

CREATE TABLE employees (
    employee_id NUMBER,
    salary      NUMBER
        CONSTRAINT emp_salary_ck CHECK (salary >= 0)
);

Example view check option:

CREATE OR REPLACE VIEW high_salary_employees AS
SELECT employee_id,
       salary
FROM employees
WHERE salary >= 50000
WITH CHECK OPTION;

They solve different problems.

25. Can WITH CHECK OPTION replace a CHECK constraint?

Generally, no.

Suppose the business rule is:

Salary must never be negative.

That should normally be enforced with a table constraint:

CONSTRAINT emp_salary_ck CHECK (salary >= 0)

A view's WITH CHECK OPTION only controls modifications made through that particular view.

A user with another access path to the table could bypass the view.

26. Does WITH CHECK OPTION work with joins?

It can be used with views that are otherwise subject to Oracle's view-updatability rules, but joins can introduce additional restrictions.

Example:

CREATE OR REPLACE VIEW emp_dept_view AS
SELECT e.employee_id,
       e.first_name,
       e.salary,
       e.department_id,
       d.department_name
FROM employees e
JOIN departments d
ON e.department_id = d.department_id
WHERE e.department_id = 10
WITH CHECK OPTION;

The WITH CHECK OPTION reinforces the view condition:

e.department_id = 10

But it does not make every column in this join view automatically updatable.

27. What is a common real-world use case?

Suppose an application should manage only employees belonging to department 10.

Create:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

The application works with:

SELECT *
FROM dept10_employees;

and can perform valid updates:

UPDATE dept10_employees
SET salary = salary + 1000
WHERE employee_id = 101;

But it cannot use the view to move an employee to another department:

UPDATE dept10_employees
SET department_id = 20
WHERE employee_id = 101;

This makes WITH CHECK OPTION useful for restricted-subset views.

28. What is the difference between WITH CHECK OPTION and WITH READ ONLY?

This is a very common interview question.

WITH READ ONLY

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary
FROM employees
WHERE department_id = 10
WITH READ ONLY;

No DML through the view:

SELECT → ✅
INSERT → ❌
UPDATE → ❌
DELETE → ❌

WITH CHECK OPTION

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

DML may be allowed, but resulting rows must continue to satisfy the view condition.

SELECT → ✅
INSERT → condition must remain true
UPDATE → condition must remain true
DELETE → generally allowed if view is otherwise deletable

Easy way to remember:

READ ONLY
   ↓
No DML

CHECK OPTION
   ↓
DML allowed
   ↓
But don't violate the view condition

29. What happens if a row is inserted through a view but the WHERE condition isn't satisfied?

Example:

CREATE OR REPLACE VIEW dept10_employees AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

Attempt:

INSERT INTO dept10_employees
VALUES (301, 'MIKE', 60000, 20);

Oracle rejects it because:

Inserted department_id = 20

View requires:
department_id = 10

The new row would not belong to the view.

30. What is the most important interview scenario?

Consider:

CREATE OR REPLACE VIEW emp_view AS
SELECT employee_id,
       first_name,
       salary,
       department_id
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;

Then:

UPDATE emp_view
SET department_id = 20
WHERE employee_id = 101;

Interview answer

The update is rejected because WITH CHECK OPTION requires the resulting row to continue satisfying:

department_id = 10

Changing it to:

department_id = 20

violates the view's WHERE condition.

31. What are the common WITH CHECK OPTION interview traps?

Trap 1

Does WITH CHECK OPTION make a view updatable?

❌ No.

It only restricts DML through a view that is otherwise updatable.

Trap 2

Does it apply to SELECT?

❌ No.

It controls DML through the view.

Trap 3

Does it prevent direct updates to the base table?

❌ No.

It applies to DML performed through the view.

Trap 4

Does it prevent DELETE?

Generally ❌ no.

Its key purpose is to prevent INSERT/UPDATE from producing rows outside the view's condition.

Trap 5

Can it be used with aggregate views to make them updatable?

❌ No.

An aggregate view remains subject to normal view-updatability restrictions.

Trap 6

What happens if an UPDATE causes a row to leave the view?

❌ The update is rejected.

Trap 7

What is the difference between LOCAL and CASCADED?

LOCAL
↓
Current view condition

CASCADED
↓
Current + underlying view conditions

32. WITH CHECK OPTION Cheat Sheet

VIEW
 |
 ↓
WHERE condition
 |
 ↓
WITH CHECK OPTION
 |
 +--------+--------+
 |                 |
INSERT            UPDATE
 |                 |
 +--------+--------+
          |
          ↓
Does resulting row
satisfy view condition?
          |
     +----+----+
     |         |
    YES        NO
     |         |
  Allowed    Rejected

Key points to remember

Feature WITH CHECK OPTION
Used with views ✅
Controls DML through view ✅
Mainly important for INSERT/UPDATE ✅
Ensures row satisfies view condition ✅
Makes non-updatable view updatable ❌
Prevents direct base-table DML ❌
Same as table CHECK constraint ❌
WITH LOCAL CHECK OPTION Current view condition
WITH CASCADED CHECK OPTION Current + underlying conditions
Default when neither specified CASCADED

Most important statement

WITH CHECK OPTION ensures that rows inserted or updated through a view continue to satisfy the view's WHERE condition.

And remember the distinction:

WITH READ ONLY
      ↓
DML NOT allowed

WITH CHECK OPTION
      ↓
DML may be allowed
      ↓
But resulting row must satisfy
the view's WHERE condition