ORA-02266: Keys Referenced by Foreign Keys - Fix Truncate
ORA-02266: Unique/Primary Keys Referenced by Foreign Keys
Section titled “ORA-02266: Unique/Primary Keys Referenced by Foreign Keys”Error Overview
Section titled “Error Overview”Error Text: ORA-02266: unique/primary keys in table referenced by enabled foreign keys
The ORA-02266 error occurs when you attempt to TRUNCATE a table whose primary or unique key is referenced by an enabled foreign key constraint in another table. Oracle prevents the truncation because it cannot verify referential integrity without scanning every child table row by row — and TRUNCATE is a DDL operation that bypasses row-level checks. This error is distinct from ORA-02449, which appears when attempting to DROP a column or constraint, and from ORA-02291/ORA-02292 which are DML-time foreign key violations.
Common Causes
Section titled “Common Causes”1. TRUNCATE on a Parent Table with Child References
Section titled “1. TRUNCATE on a Parent Table with Child References”- Attempting to truncate a lookup or reference table that has dependent child rows
- Data warehouse dimension table referenced by fact tables cannot be truncated directly
- ETL refresh process trying to reload a parent table without first handling the children
2. Test Data Reset Scripts
Section titled “2. Test Data Reset Scripts”- Development database cleanup scripts truncating tables in the wrong order
- Test teardown procedures not accounting for foreign key dependency order
TRUNCATE TABLEissued without checking referencing constraints first
3. Cascading Truncate Not Used
Section titled “3. Cascading Truncate Not Used”- Oracle 12c+ supports
TRUNCATE TABLE ... CASCADEbut it must be explicitly specified - Older scripts ported from other databases that support automatic cascade truncation
- DBA unaware of the CASCADE option
4. Constraints Re-enabled After Data Load
Section titled “4. Constraints Re-enabled After Data Load”- Foreign keys were disabled for a bulk load but re-enabled before a follow-up truncate
- Mixed state where some child foreign keys are enabled and others are disabled
Diagnostic Queries
Section titled “Diagnostic Queries”Identify All Foreign Keys Referencing a Table
Section titled “Identify All Foreign Keys Referencing a Table”-- Find all foreign keys that reference a specific parent tableSELECT c.owner AS fk_owner, c.table_name AS child_table, c.constraint_name AS fk_constraint, c.status AS fk_status, r.owner AS parent_owner, r.table_name AS parent_table, r.constraint_name AS parent_constraintFROM dba_constraints cJOIN dba_constraints r ON c.r_constraint_name = r.constraint_name AND c.r_owner = r.ownerWHERE r.table_name = UPPER('&parent_table_name') AND r.owner = UPPER('&parent_owner') AND c.constraint_type = 'R'ORDER BY c.owner, c.table_name;
-- Check for child rows that would block truncateSELECT c.table_name AS child_table, c.constraint_name AS fk_nameFROM dba_constraints cJOIN dba_constraints p ON c.r_constraint_name = p.constraint_nameWHERE p.table_name = UPPER('&parent_table_name') AND p.owner = USER AND c.status = 'ENABLED';Check Constraint Status Across a Schema
Section titled “Check Constraint Status Across a Schema”-- List all foreign key constraints and their statusSELECT owner, table_name, constraint_name, status, validated, rely, last_changeFROM dba_constraintsWHERE constraint_type = 'R' AND owner = USERORDER BY table_name, constraint_name;
-- Find tables with both enabled and disabled foreign keys (mixed state)SELECT table_name, status, COUNT(*) AS fk_countFROM dba_constraintsWHERE constraint_type = 'R' AND owner = USERGROUP BY table_name, statusORDER BY table_name;Map Full Parent-Child Dependency Tree
Section titled “Map Full Parent-Child Dependency Tree”-- Hierarchical query to map FK dependency chainSELECT LPAD(' ', 2 * (LEVEL - 1)) || table_name AS dependency_tree, constraint_name, status, LEVEL AS depthFROM dba_constraintsSTART WITH table_name = UPPER('&root_table') AND constraint_type IN ('P', 'U') AND owner = USERCONNECT BY PRIOR constraint_name = r_constraint_name AND PRIOR owner = r_ownerORDER SIBLINGS BY table_name;Step-by-Step Resolution
Section titled “Step-by-Step Resolution”1. Use TRUNCATE TABLE … CASCADE (Oracle 12c+)
Section titled “1. Use TRUNCATE TABLE … CASCADE (Oracle 12c+)”The simplest fix on Oracle 12c and later is to use the CASCADE option, which automatically truncates all child tables:
-- Truncate parent and all referencing child tables in one statementTRUNCATE TABLE parent_table CASCADE;
-- Verify all affected tables are now emptySELECT table_name, num_rowsFROM user_tablesWHERE table_name IN ('PARENT_TABLE', 'CHILD_TABLE_A', 'CHILD_TABLE_B')ORDER BY table_name;Warning: CASCADE truncates all child tables. Verify this is the intended behavior before using it in production.
2. Disable Foreign Keys, Truncate, Re-enable
Section titled “2. Disable Foreign Keys, Truncate, Re-enable”When you want to truncate only the parent table and reload data while preserving child structure:
-- Step 1: Disable all foreign keys referencing the parentBEGIN FOR fk IN ( SELECT c.owner, c.table_name, c.constraint_name FROM dba_constraints c JOIN dba_constraints p ON c.r_constraint_name = p.constraint_name AND c.r_owner = p.owner WHERE p.table_name = 'PARENT_TABLE' AND p.owner = USER AND c.constraint_type = 'R' AND c.status = 'ENABLED' ) LOOP EXECUTE IMMEDIATE 'ALTER TABLE ' || fk.owner || '.' || fk.table_name || ' DISABLE CONSTRAINT ' || fk.constraint_name; DBMS_OUTPUT.PUT_LINE('Disabled: ' || fk.constraint_name); END LOOP;END;/
-- Step 2: Truncate the parent tableTRUNCATE TABLE parent_table;
-- Step 3: Reload parent dataINSERT INTO parent_table SELECT * FROM parent_table_staging;COMMIT;
-- Step 4: Re-enable all foreign keysBEGIN FOR fk IN ( SELECT c.owner, c.table_name, c.constraint_name FROM dba_constraints c JOIN dba_constraints p ON c.r_constraint_name = p.constraint_name AND c.r_owner = p.owner WHERE p.table_name = 'PARENT_TABLE' AND p.owner = USER AND c.constraint_type = 'R' AND c.status = 'DISABLED' ) LOOP EXECUTE IMMEDIATE 'ALTER TABLE ' || fk.owner || '.' || fk.table_name || ' ENABLE CONSTRAINT ' || fk.constraint_name; DBMS_OUTPUT.PUT_LINE('Enabled: ' || fk.constraint_name); END LOOP;END;/3. Use DELETE Instead of TRUNCATE
Section titled “3. Use DELETE Instead of TRUNCATE”When referential integrity must be maintained (no CASCADE, no disabling), use DELETE:
-- DELETE respects foreign key constraints and rolls back if violated-- Must delete child rows first, then parent
-- Step 1: Delete child rowsDELETE FROM child_table WHERE parent_id IN (SELECT id FROM parent_table);-- Or: DELETE FROM child_table; if truncating all
-- Step 2: Delete parent rowsDELETE FROM parent_table;COMMIT;
-- Note: DELETE is slower than TRUNCATE for large tables but is transactional-- Reclaim space after large DELETEALTER TABLE parent_table MOVE;ALTER INDEX pk_parent_table REBUILD;4. Truncate Child Tables First (Dependency Order)
Section titled “4. Truncate Child Tables First (Dependency Order)”When you need to empty all tables in a schema, truncate in the correct order (children before parents):
-- Generate TRUNCATE statements in correct dependency order-- Children first, then parentsSELECT 'TRUNCATE TABLE ' || table_name || ';' AS truncate_stmt, LEVEL AS depthFROM dba_constraintsWHERE constraint_type IN ('P', 'U') AND owner = USERSTART WITH table_name NOT IN ( SELECT DISTINCT p.table_name FROM dba_constraints c JOIN dba_constraints p ON c.r_constraint_name = p.constraint_name WHERE c.constraint_type = 'R' AND c.owner = USER)CONNECT BY PRIOR constraint_name = r_constraint_nameORDER BY LEVEL DESC, table_name;5. Emergency Procedure: Recreate Constraints After Truncate
Section titled “5. Emergency Procedure: Recreate Constraints After Truncate”If data consistency allows, drop and recreate the foreign key constraints:
-- Save constraint definition firstSELECT 'ALTER TABLE ' || c.table_name || ' ADD CONSTRAINT ' || c.constraint_name || ' FOREIGN KEY (' || cc.column_name || ')' || ' REFERENCES ' || p.table_name || '(' || pc.column_name || ');' AS recreate_stmtFROM dba_constraints cJOIN dba_cons_columns cc ON c.constraint_name = cc.constraint_name AND c.owner = cc.ownerJOIN dba_constraints p ON c.r_constraint_name = p.constraint_name AND c.r_owner = p.ownerJOIN dba_cons_columns pc ON p.constraint_name = pc.constraint_name AND p.owner = pc.ownerWHERE c.r_owner = USER AND p.table_name = 'PARENT_TABLE' AND c.constraint_type = 'R';Prevention Strategies
Section titled “Prevention Strategies”1. Automate ETL Truncate Order
Section titled “1. Automate ETL Truncate Order”-- Stored procedure to truncate a set of tables in safe dependency orderCREATE OR REPLACE PROCEDURE truncate_schema_tables ASBEGIN -- Disable all FK constraints in the schema FOR c IN ( SELECT owner, table_name, constraint_name FROM dba_constraints WHERE constraint_type = 'R' AND owner = USER AND status = 'ENABLED' ) LOOP EXECUTE IMMEDIATE 'ALTER TABLE ' || c.owner || '.' || c.table_name || ' DISABLE CONSTRAINT ' || c.constraint_name; END LOOP;
-- Truncate all tables FOR t IN (SELECT table_name FROM user_tables ORDER BY table_name) LOOP EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || t.table_name; END LOOP;
-- Re-enable all FK constraints FOR c IN ( SELECT owner, table_name, constraint_name FROM dba_constraints WHERE constraint_type = 'R' AND owner = USER AND status = 'DISABLED' ) LOOP EXECUTE IMMEDIATE 'ALTER TABLE ' || c.owner || '.' || c.table_name || ' ENABLE CONSTRAINT ' || c.constraint_name; END LOOP;END;/2. Document Table Dependency Order
Section titled “2. Document Table Dependency Order”- Maintain a schema diagram showing FK parent-child relationships
- Store the dependency-ordered truncate sequence in your runbook
- Use Oracle SQL Developer’s table dependency view to generate the correct order
3. Staging Table Pattern for ETL
Section titled “3. Staging Table Pattern for ETL”- Load new data into staging tables, then swap with the production tables
- Use partition exchange or rename operations to avoid truncate/FK conflicts
- Consider deferrable constraints for ETL windows where order of load is flexible
4. Test Data Management
Section titled “4. Test Data Management”-- Use DBMS_METADATA to script FK constraints before disablingSELECT DBMS_METADATA.GET_DDL('CONSTRAINT', constraint_name, owner)FROM dba_constraintsWHERE constraint_type = 'R' AND owner = USER AND table_name = 'MY_TABLE';