ORA-00942: Table or View Does Not Exist β Full Troubleshooting
- ORA-00942 fires for both missing objects and missing privileges β the error message deliberately does not distinguish between them for security reasons
- Public synonyms create invisible, database-wide dependencies β dropping one breaks every unqualified reference in every schema that resolves through it
- Role-based grants do not work for definer's rights PL/SQL β use direct grants to the procedure owner or switch to AUTHID CURRENT_USER
- ORA-00942 means the database cannot resolve the object name at the current scope
- The object may exist but be inaccessible due to missing grants, wrong schema, or dropped synonyms
- Unlike ORA-06550 (compile-time), ORA-00942 is a runtime error that crashes the executing SQL statement
- It propagates as ORA-06512 through the PL/SQL call stack like any other unhandled exception
- Production insight: roughly 40% of ORA-00942 incidents trace back to missing or broken synonyms, not missing tables
- Biggest mistake: assuming the table was dropped when it actually exists in a different schema or the user simply lacks the grant to see it
Production Incident
Production Debug GuideFrom error message to root cause resolution
ORA-00942: table or view does not exist is raised at runtime when Oracle cannot resolve an object reference inside a SQL statement. The object name may be correct in the source code, but the database cannot find it in the current execution context.
The error is deceptively simple. The table exists. The name is spelled correctly. The code worked yesterday. Yet the statement fails. The cause is almost always a context change: a missing synonym, a revoked privilege, a schema migration, or a dropped public synonym after a cleanup script. Each root cause demands a different diagnostic path, and conflating them wastes hours.
ORA-00942 is one of the most common root causes of ORA-06512 in production PL/SQL systems. A procedure that references a table via an unqualified name or a synonym will compile without complaint but fail at runtime the moment the synonym is dropped or the privilege is revoked. The resulting error stack shows ORA-00942 as the originating error followed by one or more ORA-06512 entries tracing the call chain back to the top-level caller.
This guide walks through Oracle's name resolution order, the three major root cause categories β missing objects, missing synonyms, and missing privileges β and closes with automated prevention checks you can schedule today.
Name Resolution: How Oracle Finds Your Table
Oracle resolves object names through a deterministic sequence. Understanding this sequence is the single most important concept for diagnosing ORA-00942.
When Oracle encounters an unqualified table name β a name without a schema prefix β it searches in the following order: 1. It checks the current schema for a table or view with that name. 2. If no local object is found, it checks for a private synonym in the current schema. 3. If no private synonym is found, it checks for a public synonym. 4. If none of the above resolve, ORA-00942 is raised.
When Oracle encounters a qualified table name (schema.table), it bypasses synonym lookup entirely and goes directly to the specified schema. If the object does not exist in that schema, or the user lacks privilege to see it, ORA-00942 is raised.
The privilege check happens after name resolution succeeds. Oracle first resolves the name to a specific schema and object, then verifies that the executing user holds the required privilege (typically SELECT) on that resolved object. If the name resolves but the privilege is missing, Oracle still raises ORA-00942 β not ORA-01031 (insufficient privileges). This is by design: Oracle's security model prevents confirming the existence of objects to users who lack access. It is also the single most confusing aspect of this error, because the message says "does not exist" when the real problem is a missing grant.
In PL/SQL, the AUTHID clause determines whose privileges apply during execution. Definer's rights procedures (AUTHID DEFINER, the default) use the procedure owner's privileges and synonym context for name resolution. Invoker's rights procedures (AUTHID CURRENT_USER) use the calling user's privileges and synonym context. This distinction governs which synonyms are visible and which grants are effective at runtime.
-- Demonstrate Oracle name resolution order -- Run as user APP_USER querying the ORDERS table -- Step 1: Check for a table or view in the current schema SELECT object_name, object_type FROM user_objects WHERE object_name = 'ORDERS' AND object_type IN ('TABLE', 'VIEW'); -- If found: resolves to current_schema.ORDERS -- Step 2: Check for a private synonym in the current schema SELECT synonym_name, table_owner, table_name FROM user_synonyms WHERE synonym_name = 'ORDERS'; -- If found: resolves to table_owner.table_name -- Step 3: Check for a public synonym SELECT synonym_name, table_owner, table_name FROM all_synonyms WHERE synonym_name = 'ORDERS' AND owner = 'PUBLIC'; -- If found: resolves to table_owner.table_name -- Step 4: Check across all accessible schemas SELECT owner, object_name, object_type FROM all_objects WHERE object_name = 'ORDERS' AND object_type IN ('TABLE', 'VIEW') ORDER BY owner; -- Shows every schema that contains an ORDERS table/view -- Step 5: Check privileges on the resolved object SELECT grantor, grantee, privilege, grantable FROM all_tab_privs WHERE table_name = 'ORDERS' ORDER BY grantee, privilege; -- Shows who holds SELECT, INSERT, UPDATE, DELETE on ORDERS -- Step 6: Trace the full resolution path in one query SELECT 'Local Object' AS resolution_type, object_name AS name, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') || '.' || object_name AS resolves_to FROM user_objects WHERE object_name = 'ORDERS' AND object_type IN ('TABLE', 'VIEW') UNION ALL SELECT 'Private Synonym', synonym_name, table_owner || '.' || table_name FROM user_synonyms WHERE synonym_name = 'ORDERS' UNION ALL SELECT 'Public Synonym', synonym_name, table_owner || '.' || table_name FROM all_synonyms WHERE synonym_name = 'ORDERS' AND owner = 'PUBLIC'; -- The first row returned is the resolution Oracle would use
- Unqualified name (ORDERS): current schema object β private synonym β public synonym β ORA-00942
- Qualified name (OWNER.ORDERS): direct lookup in specified schema β ORA-00942
- Privilege check happens AFTER resolution β a missing grant raises ORA-00942, not ORA-01031
- Definer's rights procedures (AUTHID DEFINER) use the owner's privileges and synonyms for resolution
- Invoker's rights procedures (AUTHID CURRENT_USER) use the caller's privileges and synonyms for resolution
Synonym Failures: The Invisible Dependency
Synonyms are the most common cause of ORA-00942 in multi-schema Oracle environments. They create implicit, invisible dependencies that span schemas β and dropping a synonym can break every unqualified reference that depends on it.
Public synonyms are the most dangerous because they are database-wide. Every schema in the instance can resolve an unqualified name through a public synonym. A single DROP PUBLIC SYNONYM command can break procedures, views, materialized view queries, and triggers across dozens of schemas simultaneously. The dependency is invisible because DBA_DEPENDENCIES does not reliably track public synonym resolution by PL/SQL objects β the dependency graph shows the synonym target, not the synonym itself.
Private synonyms are safer because they are schema-scoped. Dropping a private synonym only affects objects within that schema. But private synonyms still create implicit dependencies that are easy to overlook during schema reorganizations.
Broken synonyms β synonyms whose target object has been dropped or renamed β raise ORA-00980 (synonym translation is no longer valid) rather than ORA-00942. However, if the synonym itself is dropped, the resolution chain never reaches the synonym and ORA-00942 is raised instead. This distinction matters during diagnosis: ORA-00980 means a synonym exists but points to nothing; ORA-00942 means no synonym was found at all.
The diagnostic query for synonym-related ORA-00942 checks three locations: the current schema for private synonyms, PUBLIC for public synonyms, and the target schema for the actual object. If the object exists in another schema but no synonym points to it, the fix is either creating the synonym or qualifying the table name in the source code.
The permanent fix is to use fully qualified table names (schema.table) for all critical references. This eliminates synonym dependency entirely. The trade-off is code verbosity and coupling to the schema name, but in production the reliability gain is overwhelming.
-- Diagnostic 1: Find all public synonyms actively used by application objects -- Run this BEFORE any synonym cleanup or decommission script SELECT DISTINCT d.referenced_name AS synonym_or_table_name, d.referenced_owner AS target_schema, d.owner AS dependent_schema, d.name AS dependent_object, d.type AS dependent_type FROM dba_dependencies d WHERE d.referenced_type IN ('TABLE', 'VIEW') AND d.owner IN ('APP_SCHEMA1', 'APP_SCHEMA2', 'APP_SCHEMA3') AND d.referenced_name IN ( SELECT synonym_name FROM dba_synonyms WHERE owner = 'PUBLIC' ) ORDER BY d.referenced_name, d.owner; -- Diagnostic 2: Check if a specific synonym exists and where it points SELECT owner AS synonym_owner, synonym_name, table_owner AS target_owner, table_name AS target_object, db_link FROM all_synonyms WHERE synonym_name = 'ORDERS' ORDER BY DECODE(owner, 'PUBLIC', 2, 1), owner; -- Private synonyms appear first, public synonyms second -- Diagnostic 3: Find every PL/SQL object that depends on a synonym SELECT owner, name, type, referenced_name, referenced_type FROM dba_dependencies WHERE referenced_name = 'ORDERS' AND referenced_type IN ('SYNONYM', 'TABLE', 'VIEW') ORDER BY owner, type, name; -- Diagnostic 4: Recreate a dropped public synonym CREATE OR REPLACE PUBLIC SYNONYM orders FOR owner.orders; -- All procedures that reference 'ORDERS' without qualification -- will resolve again immediately β no recompilation required -- Diagnostic 5: Find all unqualified table references in PL/SQL source -- These references are vulnerable to synonym drops SELECT owner, name, type, line, TRIM(text) AS source_line FROM all_source WHERE UPPER(text) LIKE '%FROM ORDERS%' OR UPPER(text) LIKE '%INTO ORDERS%' OR UPPER(text) LIKE '%UPDATE ORDERS%' OR UPPER(text) LIKE '%DELETE FROM ORDERS%' ORDER BY owner, name, line; -- Review results: any line that does NOT include 'OWNER.ORDERS' -- is relying on a synonym and is vulnerable -- Diagnostic 6: Verify the target object exists and is VALID SELECT owner, object_name, object_type, status, last_ddl_time FROM all_objects WHERE object_name = 'ORDERS' AND object_type IN ('TABLE', 'VIEW', 'SYNONYM') ORDER BY owner, object_type;
Privilege Failures: ORA-00942 Masking ORA-01031
Oracle raises ORA-00942 when the executing user lacks SELECT privilege on a table or view. The object exists, the name is correct, the synonym resolves β but the user cannot see the resolved object. This is the most confusing variant of ORA-00942 because the error message says "table or view does not exist" when the real problem is a missing grant.
The reason is Oracle's security model: confirming an object's existence to a user who lacks access is treated as an information disclosure vulnerability. If Oracle raised ORA-01031 (insufficient privileges), the user would learn that the object exists. Instead, Oracle returns the same generic ORA-00942 for both conditions, keeping the object's existence opaque.
The diagnostic path is straightforward once you know to check: first, verify the object exists by querying DBA_OBJECTS as a DBA user. If the object exists, query DBA_TAB_PRIVS and DBA_ROLE_PRIVS to check whether the executing user has the required privilege either directly or through a role.
For definer's rights PL/SQL (AUTHID DEFINER, the default), the procedure owner needs the direct grant β not the calling user. For invoker's rights PL/SQL (AUTHID CURRENT_USER), the calling user needs the grant. This distinction is critical because it determines which user's privileges Oracle evaluates at runtime.
Role-based grants introduce an additional subtlety. Roles are active during interactive SQL sessions and inside invoker's rights PL/SQL, but they are not active during definer's rights PL/SQL compilation or execution. If a table privilege is granted through a role, a definer's rights procedure cannot use it. The fix is a direct GRANT to the procedure owner β or switching the procedure to AUTHID CURRENT_USER.
This is one of the most frequently misdiagnosed ORA-00942 scenarios. Engineers see the error, confirm the table exists, confirm a grant exists through a role, and conclude that Oracle is buggy. The real problem is the definer's rights compilation model ignoring role-based grants.
-- Diagnostic 1: Verify the object exists (run as DBA) SELECT owner, object_name, object_type, status, last_ddl_time FROM dba_objects WHERE object_name = 'ORDERS' AND object_type IN ('TABLE', 'VIEW'); -- If rows returned: the object exists β problem is privilege or synonym -- If no rows: the object was dropped or never created -- Diagnostic 2: Check direct grants on the object SELECT grantor, grantee, privilege, grantable FROM dba_tab_privs WHERE table_name = 'ORDERS' ORDER BY grantee, privilege; -- Look for the executing user or the procedure owner in the grantee column -- Diagnostic 3: Check if the user can see the object via ALL_OBJECTS -- Run this connected AS the application user, not as DBA SELECT owner, object_name, object_type FROM all_objects WHERE object_name = 'ORDERS' AND object_type IN ('TABLE', 'VIEW'); -- No rows: the user cannot see the object (privilege or synonym issue) -- Rows returned: the user can see it (problem is elsewhere) -- Diagnostic 4: Check role-based grants -- These work for direct SQL and invoker's rights PL/SQL -- but NOT for definer's rights PL/SQL SELECT r.role, rp.table_name, rp.privilege, rp.grantable FROM dba_role_privs r JOIN role_tab_privs rp ON rp.role = r.granted_role WHERE r.grantee = 'APP_USER' AND rp.table_name = 'ORDERS' ORDER BY r.role, rp.privilege; -- Diagnostic 5: Check the AUTHID of the failing procedure SELECT owner, object_name, authid FROM all_procedures WHERE object_name = 'GET_ORDERS' AND owner = 'APP_SCHEMA'; -- DEFINER = owner's privileges apply (roles ignored) -- CURRENT_USER = caller's privileges apply (roles active) -- Fix 1: Direct grant to the schema owner (works for definer's rights) GRANT SELECT ON owner.orders TO app_schema; -- Fix 2: Direct grant to the calling user (works for invoker's rights) GRANT SELECT ON owner.orders TO app_user; -- Fix 3: Switch procedure to invoker's rights if role-based grants must be used CREATE OR REPLACE PROCEDURE app_schema.get_orders( p_order_id IN NUMBER ) AUTHID CURRENT_USER -- Caller's privileges, roles active IS v_rec owner.orders%ROWTYPE; BEGIN SELECT * INTO v_rec FROM owner.orders -- Fully qualified for safety WHERE order_id = p_order_id; END get_orders; /
- ORA-00942 for a missing object: the table was dropped or never created β verify with DBA_OBJECTS
- ORA-00942 for a missing privilege: the table exists but the user cannot see it β verify with DBA_TAB_PRIVS
- Oracle deliberately does not distinguish between the two because revealing object existence to unauthorized users is a security risk
- Diagnostic step one: query DBA_OBJECTS as DBA β if the object exists, the problem is privilege, not existence
- Definer's rights PL/SQL ignores role-based grants β only direct grants to the procedure owner are effective
Schema Qualification: The Permanent Fix
Fully qualified table names (schema.table) eliminate three classes of ORA-00942 failures at once: synonym drops, cross-schema resolution failures, and privilege confusion caused by synonym chains. The name resolves directly to the target schema without any synonym lookup step.
The trade-off is code verbosity and coupling to the schema name. Every table reference includes the schema prefix, which means renaming a schema or migrating to a different environment with different schema names requires updating every reference. In practice, schema renames are rare β they happen maybe once in the lifetime of a system β and the reliability gain far outweighs the coupling cost in production.
The migration pattern is incremental. You do not need to rewrite every SQL statement in a single sprint. Start by qualifying references to the most critical tables β the ones whose unavailability would cause the most business damage. Use static analysis queries against ALL_SOURCE to find unqualified references and prioritize by table criticality and call frequency.
For dynamic SQL, schema qualification must be enforced at the query construction level. Define a configuration package or a constant map that associates logical table names with fully qualified physical names. This centralizes the schema dependency into a single compilation unit and makes migration to a different schema a one-line configuration change rather than a grep-and-replace across hundreds of files.
Views deserve special attention. A view that references an unqualified table name inherits the synonym dependency of the schema where the view is compiled. If that synonym is dropped, the view becomes INVALID and any query against it raises ORA-04063 (which often cascades into ORA-00942 in the caller). Always use fully qualified names inside view definitions.
-- Pattern 1: Fully qualified names in PL/SQL -- Eliminates synonym dependency entirely CREATE OR REPLACE PROCEDURE app_schema.get_order( p_order_id IN NUMBER, p_order OUT data_owner.orders%ROWTYPE ) IS BEGIN SELECT * INTO p_order FROM data_owner.orders -- Fully qualified: schema.table WHERE order_id = p_order_id; -- Works regardless of synonym state EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20010, 'Order not found: ' || p_order_id); END get_order; / -- Pattern 2: Centralized table name constants for dynamic SQL CREATE OR REPLACE PACKAGE app_schema.table_ref AS -- Single source of truth for all schema-qualified table names c_orders CONSTANT VARCHAR2(128) := 'DATA_OWNER.ORDERS'; c_customers CONSTANT VARCHAR2(128) := 'DATA_OWNER.CUSTOMERS'; c_payments CONSTANT VARCHAR2(128) := 'DATA_OWNER.PAYMENTS'; c_audit_log CONSTANT VARCHAR2(128) := 'DATA_OWNER.AUDIT_LOG'; -- To migrate schemas, change the constants here and recompile END table_ref; / -- Pattern 3: Using the constant in dynamic SQL CREATE OR REPLACE PROCEDURE app_schema.count_by_status( p_status IN VARCHAR2, p_count OUT NUMBER ) IS v_sql VARCHAR2(4000); BEGIN v_sql := 'SELECT COUNT(*) FROM ' || app_schema.table_ref.c_orders || ' WHERE status = :b_status'; EXECUTE IMMEDIATE v_sql INTO p_count USING p_status; END count_by_status; / -- Pattern 4: Views with fully qualified names CREATE OR REPLACE VIEW app_schema.active_orders_v AS SELECT o.order_id, o.customer_id, o.amount, o.status, c.customer_name FROM data_owner.orders o -- Fully qualified JOIN data_owner.customers c -- Fully qualified ON c.customer_id = o.customer_id WHERE o.status = 'ACTIVE'; -- This view remains VALID regardless of synonym state / -- Pattern 5: Find all unqualified table references in a schema -- Use this to plan an incremental migration to qualified names SELECT owner, name, type, line, TRIM(text) AS source_line FROM all_source WHERE owner = 'APP_SCHEMA' AND type IN ('PACKAGE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER') AND ( REGEXP_LIKE(UPPER(text), '\bFROM\s+[A-Z_][A-Z0-9_]*\b') OR REGEXP_LIKE(UPPER(text), '\bJOIN\s+[A-Z_][A-Z0-9_]*\b') OR REGEXP_LIKE(UPPER(text), '\bINTO\s+[A-Z_][A-Z0-9_]*\b') OR REGEXP_LIKE(UPPER(text), '\bUPDATE\s+[A-Z_][A-Z0-9_]*\b') OR REGEXP_LIKE(UPPER(text), '\bDELETE\s+FROM\s+[A-Z_][A-Z0-9_]*\b') ) AND NOT REGEXP_LIKE(UPPER(text), '\b[A-Z_][A-Z0-9_]*\.[A-Z_][A-Z0-9_]*\b') ORDER BY owner, name, line; -- Each row is an unqualified reference that depends on a synonym
- Start with the most critical tables β qualify references to tables whose unavailability causes the most business impact
- Use the static analysis query (Pattern 5) to find all unqualified references β prioritize by table criticality and call frequency
- Centralize table name constants in a configuration package β makes schema migration a single recompile
- Always use qualified names inside view definitions β an unqualified reference makes the view dependent on a synonym
- Migrate incrementally: qualify one table at a time, test, deploy, repeat β do not attempt a full rewrite in a single change
Dynamic SQL and ORA-00942
Dynamic SQL introduces additional ORA-00942 risk because the object name is constructed at runtime as a string. The PL/SQL compiler cannot validate the reference at compile time β it is just a VARCHAR2 that may or may not resolve correctly when Oracle executes it.
The most common dynamic SQL ORA-00942 is caused by incorrect string concatenation. A missing space between FROM and the table name, an extra single quote, a wrong case when the table was created with double-quoted mixed-case identifiers β any of these produce a syntactically valid SQL string that references a non-existent object. The error message shows the constructed SQL, not the PL/SQL source line, which makes debugging painful if you did not log the string.
Another common cause is privilege context. EXECUTE IMMEDIATE uses the current session user's privileges, even when the enclosing procedure is compiled with AUTHID DEFINER. This is a subtle but critical distinction from static SQL. If the procedure owner has SELECT on a table but the calling user does not, static SQL in the same procedure succeeds while the dynamic SQL raises ORA-00942. The fix is either granting the privilege directly to the caller, using fully qualified names with a direct grant, or restructuring the dynamic SQL into a static cursor.
The third cause is synonym visibility. If the dynamic SQL uses an unqualified table name and the session user does not have a private synonym, and the public synonym was dropped, ORA-00942 is raised. The dynamic SQL is resolving names using the session user's synonym context, not the procedure owner's.
The non-negotiable practice for dynamic SQL is to log the fully constructed SQL string before execution. Without the actual string, ORA-00942 in dynamic SQL is a black box. DBMS_OUTPUT works for development; a dedicated logging table or the application's structured logging framework works for production.
-- WRONG: Dynamic SQL with unqualified name and no logging CREATE OR REPLACE PROCEDURE app_schema.broken_dynamic( p_status IN VARCHAR2 ) IS v_sql VARCHAR2(4000); v_count NUMBER; BEGIN -- Problem 1: unqualified name β depends on synonym -- Problem 2: no logging β impossible to debug -- Problem 3: string concatenation for value β SQL injection risk v_sql := 'SELECT COUNT(*) FROM ORDERS WHERE status = ''' || p_status || ''''; EXECUTE IMMEDIATE v_sql INTO v_count; END broken_dynamic; / -- CORRECT: Dynamic SQL with qualified name, logging, and bind variables CREATE OR REPLACE PROCEDURE app_schema.safe_dynamic( p_status IN VARCHAR2 ) IS v_sql VARCHAR2(4000); v_count NUMBER; BEGIN -- Fully qualified name β no synonym dependency -- Bind variable β no SQL injection, no quoting errors v_sql := 'SELECT COUNT(*) FROM data_owner.orders WHERE status = :b_status'; -- Log the SQL before execution β critical for ORA-00942 diagnosis DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql); EXECUTE IMMEDIATE v_sql INTO v_count USING p_status; DBMS_OUTPUT.PUT_LINE('Result: ' || v_count); EXCEPTION WHEN OTHERS THEN -- Log the failed SQL with the error β the only way to diagnose runtime failures DBMS_OUTPUT.PUT_LINE('FAILED SQL: ' || v_sql); DBMS_OUTPUT.PUT_LINE('ERROR: ' || SQLERRM); RAISE; -- Re-raise after logging END safe_dynamic; / -- CORRECT: Dynamic SQL with privilege context awareness CREATE OR REPLACE PROCEDURE app_schema.privilege_aware_dynamic AUTHID CURRENT_USER -- Uses caller's privileges IS v_sql VARCHAR2(4000); v_count NUMBER; BEGIN -- AUTHID CURRENT_USER: EXECUTE IMMEDIATE uses the CALLER's privileges -- If the caller lacks SELECT on data_owner.orders: ORA-00942 v_sql := 'SELECT COUNT(*) FROM data_owner.orders WHERE status = :b_status'; BEGIN EXECUTE IMMEDIATE v_sql INTO v_count USING 'ACTIVE'; DBMS_OUTPUT.PUT_LINE('Count: ' || v_count); EXCEPTION WHEN OTHERS THEN RAISE_APPLICATION_ERROR( -20001, 'Dynamic SQL failed. ' || 'SQL: ' || v_sql || ' | Error: ' || SQLERRM || ' | Session User: ' || SYS_CONTEXT('USERENV', 'SESSION_USER') || ' | Note: AUTHID CURRENT_USER means caller privileges apply', TRUE -- Preserve the original error stack ); END; END privilege_aware_dynamic; /
Preventing ORA-00942: Automated Dependency Checks
ORA-00942 prevention requires automated checks that run at two points in the lifecycle: before deployments and on a recurring schedule. The checks verify that all referenced objects exist, are accessible, have valid synonyms, and have the required grants in place.
The pre-deployment check queries DBA_DEPENDENCIES for every object in the deployment changeset and verifies that every referenced object exists and has a VALID status. If any reference is broken β a missing table, an INVALID synonym, a dropped view β the deployment is blocked with a list of missing or invalid objects. This catches problems before they reach production.
The daily health check scans all application schemas for INVALID objects, broken synonyms (synonyms whose target has been dropped), and missing grants. It runs on a schedule and alerts the DBA team on any anomaly. The goal is to detect silent degradation β a revoked grant, a dropped synonym, a quietly invalidated package β before it triggers a runtime ORA-00942 during business hours.
The synonym impact check runs before any DROP SYNONYM command. It searches ALL_SOURCE for references to the synonym name across every application schema and reports the blast radius: how many objects in how many schemas reference the synonym. If the count is greater than zero, the drop is blocked until every reference is either updated to use a qualified name or a replacement synonym is created.
Combined, these three checks convert ORA-00942 from a runtime surprise into a pre-detected, preventable issue. The implementation cost is a handful of stored procedures and a scheduler job. The benefit is eliminating an entire class of production outages.
-- Check 1: Pre-deployment dependency validation -- Run before every deployment to verify all references resolve CREATE OR REPLACE PROCEDURE app_schema.check_dependencies( p_schema IN VARCHAR2 ) IS v_missing_count NUMBER := 0; BEGIN FOR rec IN ( SELECT d.owner, d.name, d.type, d.referenced_name, d.referenced_type, d.referenced_owner FROM dba_dependencies d WHERE d.owner = UPPER(p_schema) AND d.referenced_owner NOT IN ('SYS', 'SYSTEM', 'PUBLIC') AND NOT EXISTS ( SELECT 1 FROM dba_objects o WHERE o.owner = d.referenced_owner AND o.object_name = d.referenced_name AND o.object_type = d.referenced_type AND o.status = 'VALID' ) ORDER BY d.name, d.referenced_name ) LOOP v_missing_count := v_missing_count + 1; DBMS_OUTPUT.PUT_LINE( 'BROKEN REF: ' || rec.type || ' ' || rec.owner || '.' || rec.name || ' -> ' || rec.referenced_type || ' ' || rec.referenced_owner || '.' || rec.referenced_name ); END LOOP; IF v_missing_count > 0 THEN RAISE_APPLICATION_ERROR( -20070, 'Deployment blocked: ' || v_missing_count || ' broken object references in schema ' || p_schema ); ELSE DBMS_OUTPUT.PUT_LINE('All dependencies valid in schema ' || p_schema); END IF; END check_dependencies; / -- Check 2: Daily synonym integrity check -- Detects synonyms that point to non-existent or invalid objects CREATE OR REPLACE PROCEDURE app_schema.check_synonyms IS v_broken_count NUMBER := 0; BEGIN FOR rec IN ( SELECT s.owner AS synonym_owner, s.synonym_name, s.table_owner AS target_owner, s.table_name AS target_object FROM dba_synonyms s WHERE s.owner IN ('PUBLIC', 'APP_SCHEMA', 'APP_SCHEMA2', 'APP_SCHEMA3') AND s.db_link IS NULL -- Skip DB link synonyms for this check AND NOT EXISTS ( SELECT 1 FROM dba_objects o WHERE o.owner = s.table_owner AND o.object_name = s.table_name AND o.status = 'VALID' ) ORDER BY s.owner, s.synonym_name ) LOOP v_broken_count := v_broken_count + 1; DBMS_OUTPUT.PUT_LINE( 'BROKEN SYNONYM: ' || rec.synonym_owner || '.' || rec.synonym_name || ' -> ' || rec.target_owner || '.' || rec.target_object || ' (missing or invalid)' ); END LOOP; IF v_broken_count > 0 THEN DBMS_OUTPUT.PUT_LINE(v_broken_count || ' broken synonym(s) detected β review and fix before business hours'); ELSE DBMS_OUTPUT.PUT_LINE('All synonyms valid'); END IF; END check_synonyms; / -- Check 3: Synonym drop impact analysis -- Run BEFORE any DROP SYNONYM command to measure blast radius CREATE OR REPLACE PROCEDURE app_schema.analyze_synonym_drop( p_synonym_name IN VARCHAR2 ) IS v_reference_count NUMBER := 0; BEGIN -- Count source code references across all application schemas SELECT COUNT(*) INTO v_reference_count FROM all_source WHERE REGEXP_LIKE(UPPER(text), '\b' || UPPER(p_synonym_name) || '\b') AND owner NOT IN ('SYS', 'SYSTEM') AND type IN ('PACKAGE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER', 'VIEW'); IF v_reference_count > 0 THEN DBMS_OUTPUT.PUT_LINE( 'WARNING: ' || v_reference_count || ' source references found for synonym ' || UPPER(p_synonym_name) ); DBMS_OUTPUT.PUT_LINE( 'Dropping this synonym WILL cause ORA-00942 in the following objects:' ); FOR rec IN ( SELECT DISTINCT owner, name, type FROM all_source WHERE REGEXP_LIKE(UPPER(text), '\b' || UPPER(p_synonym_name) || '\b') AND owner NOT IN ('SYS', 'SYSTEM') AND type IN ('PACKAGE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER', 'VIEW') ORDER BY owner, type, name ) LOOP DBMS_OUTPUT.PUT_LINE(' ' || rec.type || ' ' || rec.owner || '.' || rec.name); END LOOP; RAISE_APPLICATION_ERROR( -20072, 'Drop blocked: ' || v_reference_count || ' objects reference synonym ' || UPPER(p_synonym_name) || '. Update references to qualified names before dropping.' ); ELSE DBMS_OUTPUT.PUT_LINE( 'No references found for synonym ' || UPPER(p_synonym_name) || '. Safe to drop.' ); END IF; END analyze_synonym_drop; / -- Schedule the daily checks using DBMS_SCHEDULER BEGIN DBMS_SCHEDULER.CREATE_JOB( job_name => 'APP_SCHEMA.DAILY_SYNONYM_CHECK', job_type => 'PLSQL_BLOCK', job_action => 'BEGIN app_schema.check_synonyms; END;', start_date => SYSTIMESTAMP, repeat_interval => 'FREQ=DAILY; BYHOUR=6; BYMINUTE=0', enabled => TRUE, comments => 'Daily check for broken synonyms β prevents ORA-00942 at runtime' ); END; /
- Pre-deployment: run check_dependencies to verify all referenced objects exist and are VALID
- Daily at 06:00: run check_synonyms to detect broken synonyms before business hours
- Before DROP SYNONYM: run analyze_synonym_drop to measure blast radius and block if references exist
- Weekly: check for INVALID objects: SELECT owner, object_name, object_type FROM dba_objects WHERE status = 'INVALID' AND owner IN (application schemas)
- Use fully qualified names for the top 20 most critical tables β eliminates synonym dependency where it matters most
| Root Cause | Diagnostic Query | Fix | Prevention |
|---|---|---|---|
| Table or view genuinely dropped | SELECT * FROM dba_objects WHERE object_name = 'NAME' AND object_type IN ('TABLE','VIEW') | Recreate the table or restore from backup / flashback | Pre-deployment dependency validation; daily INVALID object checks |
| Missing private or public synonym | SELECT * FROM all_synonyms WHERE synonym_name = 'NAME' | CREATE [PUBLIC] SYNONYM name FOR schema.name | Daily synonym integrity check; synonym drop impact analysis |
| Missing SELECT grant (appears as ORA-00942, not ORA-01031) | SELECT * FROM dba_tab_privs WHERE table_name = 'NAME' AND grantee = 'USER' | GRANT SELECT ON schema.table TO user | Daily grant verification; document required grants per schema |
| Wrong schema β object exists elsewhere | SELECT owner FROM all_objects WHERE object_name = 'NAME' AND object_type IN ('TABLE','VIEW') | Add schema qualification: schema.table or create a synonym | Use fully qualified names in all source code |
| Public synonym dropped during cleanup | SELECT * FROM dba_synonyms WHERE synonym_name = 'NAME' AND owner = 'PUBLIC' | Recreate: CREATE PUBLIC SYNONYM name FOR schema.name | Run analyze_synonym_drop before every DROP SYNONYM |
| Role-based grant in definer's rights procedure | Check AUTHID of the procedure via ALL_PROCEDURES | Issue a direct GRANT to the procedure owner or switch to AUTHID CURRENT_USER | Use direct grants for all definer's rights procedures; document the pattern |
π― Key Takeaways
- ORA-00942 fires for both missing objects and missing privileges β the error message deliberately does not distinguish between them for security reasons
- Public synonyms create invisible, database-wide dependencies β dropping one breaks every unqualified reference in every schema that resolves through it
- Role-based grants do not work for definer's rights PL/SQL β use direct grants to the procedure owner or switch to AUTHID CURRENT_USER
- Fully qualified table names (schema.table) eliminate synonym dependency permanently β the reference resolves without any synonym lookup
- Dynamic SQL ORA-00942 is impossible to diagnose without logging the constructed SQL string before execution
- Automated dependency and synonym integrity checks convert ORA-00942 from a runtime surprise into a detected and preventable issue
β Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat causes ORA-00942 and how do you diagnose it?JuniorReveal
- QWhy does ORA-00942 fire for missing privileges instead of ORA-01031?Mid-levelReveal
- QHow do public synonyms create invisible dependencies, and what is the blast radius of dropping one?Mid-levelReveal
- QWhy do role-based grants fail for definer's rights PL/SQL, and what are the alternatives?SeniorReveal
- QHow would you design a prevention system that eliminates ORA-00942 from a production environment?SeniorReveal
Frequently Asked Questions
What is the difference between ORA-00942 and ORA-00980?
ORA-00942 means the table or view does not exist or is not accessible β no synonym or object could be found for the given name. ORA-00980 means a synonym exists but its target object has been dropped, renamed, or is otherwise no longer valid β the synonym translation is broken. ORA-00980 specifically indicates a stale synonym, while ORA-00942 is a broader error covering missing objects, missing synonyms, and missing grants. The fix for ORA-00980 is to repair or drop the broken synonym; the fix for ORA-00942 depends on which of the three root causes applies.
How do I find which synonym is causing ORA-00942?
Query ALL_SYNONYMS for the object name: SELECT owner, synonym_name, table_owner, table_name FROM all_synonyms WHERE synonym_name = 'OBJECT_NAME' ORDER BY DECODE(owner, 'PUBLIC', 2, 1). Check both private synonyms (visible in USER_SYNONYMS) and public synonyms (where owner = 'PUBLIC'). If a synonym exists, verify its target object is present and VALID: SELECT owner, object_name, status FROM dba_objects WHERE owner = synonym's table_owner AND object_name = synonym's table_name. If no synonym exists at all, that is the cause β create one or qualify the table name.
Can ORA-00942 occur on a view that exists and is VALID?
Yes. A view can exist and show VALID status, but querying it raises ORA-00942 if its underlying base table is inaccessible to the querying user. The view compiles under the view owner's privileges, but if the querying user lacks SELECT on the view itself, or if the view's base tables have been dropped since the view was last compiled, ORA-00942 is raised. Query DBA_VIEWS to extract the view text, then check privileges on every base table and view referenced inside the definition.
How do I check if a specific user has SELECT privilege on a specific table?
Check direct grants: SELECT grantor, privilege, grantable FROM dba_tab_privs WHERE table_name = 'TABLE_NAME' AND grantee = 'USER_NAME'. Also check role-based grants: SELECT rp.role, rp.privilege FROM role_tab_privs rp JOIN dba_role_privs drp ON drp.granted_role = rp.role WHERE drp.grantee = 'USER_NAME' AND rp.table_name = 'TABLE_NAME'. Remember that role-based grants work for direct SQL and invoker's rights PL/SQL but do not work for definer's rights PL/SQL compilation or execution.
Should I use synonyms or fully qualified names in production code?
Use fully qualified names (schema.table) for critical production references. Synonyms add a layer of indirection that creates invisible dependencies, complicates troubleshooting, and introduces a failure mode (synonym drop) that qualified names are immune to. The reliability gain of qualified names outweighs the code verbosity cost. Reserve synonyms for specific use cases: cross-database links where the remote schema name may vary by environment, or when the schema name genuinely must be configurable at deployment time without recompilation. For everything else, qualify the name and eliminate the dependency.
Can ORA-00942 appear in a SELECT on a table I own in my own schema?
Yes, in rare cases. If you created the table using double-quoted mixed-case identifiers (e.g., CREATE TABLE "Orders" ...), the object name is stored in mixed case. An unquoted reference like SELECT FROM Orders resolves to the uppercase name ORDERS, which does not match the mixed-case name "Orders". The fix is to use the exact case with double quotes: SELECT FROM "Orders". This is uncommon but catches people who create tables from ORM tools that double-quote identifiers by default.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.