Skip to content

ORA-00904: Invalid Identifier

ORA-00904 occurs when Oracle cannot find or recognize a column name, table alias, or identifier referenced in a SQL statement. This is one of the most common SQL syntax errors encountered by developers and DBAs.

ORA-00904: "identifier_name": invalid identifier

Where identifier_name is the problematic column, alias, or identifier that Oracle cannot resolve.

🟡 MEDIUM - SQL compilation error that prevents query execution but doesn’t affect database operations.

  • Misspelled column names
  • Column doesn’t exist in the referenced table
  • Case sensitivity issues with quoted identifiers
  • Column name conflicts in joins
  • Using table aliases inconsistently
  • Referencing columns without proper table qualification
  • Alias scope issues in subqueries
  • Using Oracle reserved words as identifiers
  • Keywords used without proper quoting
  • Version-specific reserved word issues
  • Mixing quoted and unquoted identifiers
  • Case sensitivity problems
  • Special characters in identifiers
-- Misspelled column name
SELECT employee_id, frist_name FROM employees;
-- Error: "FRIST_NAME": invalid identifier
-- Column doesn't exist
SELECT employee_id, middle_name FROM employees;
-- Error: "MIDDLE_NAME": invalid identifier
-- Case sensitivity with quoted identifiers
CREATE TABLE test_table ("Column_Name" VARCHAR2(50));
SELECT Column_Name FROM test_table;
-- Error: "COLUMN_NAME": invalid identifier
-- Fix 1: Correct spelling
SELECT employee_id, first_name FROM employees;
-- Fix 2: Use existing columns
SELECT employee_id, last_name FROM employees;
-- Fix 3: Use exact case for quoted identifiers
SELECT "Column_Name" FROM test_table;
-- Check available columns
SELECT column_name
FROM user_tab_columns
WHERE table_name = 'EMPLOYEES'
ORDER BY column_name;
-- Missing table alias in join
SELECT employee_id, department_name
FROM employees e
JOIN departments d ON department_id = d.department_id;
-- Error: "DEPARTMENT_ID": invalid identifier (ambiguous)
-- Using wrong alias
SELECT emp.employee_id, dept.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
-- Error: "EMP": invalid identifier
-- Fix 1: Properly qualify all columns
SELECT e.employee_id, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
-- Fix 2: Use correct aliases
SELECT e.employee_id, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
-- Fix 3: Use full table names if preferred
SELECT employees.employee_id, departments.department_name
FROM employees
JOIN departments ON employees.department_id = departments.department_id;
-- Column from outer query not accessible in subquery
SELECT employee_id,
(SELECT department_name
FROM departments
WHERE department_id = e.department_id) as dept_name
FROM employees e;
-- Works fine
-- But this fails in some contexts
SELECT employee_id
FROM employees e
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = department_id -- Ambiguous reference
);
-- Fix: Always use proper table aliases in subqueries
SELECT employee_id
FROM employees e
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = e.department_id
);
-- Alternative: Use table name qualification
SELECT employee_id
FROM employees
WHERE EXISTS (
SELECT 1 FROM departments
WHERE departments.department_id = employees.department_id
);

Scenario 4: Reserved Words and Special Characters

Section titled “Scenario 4: Reserved Words and Special Characters”
-- Using reserved words as column names
CREATE TABLE test_table (
id NUMBER,
order NUMBER, -- 'order' is reserved
date VARCHAR2(10) -- 'date' is reserved
);
SELECT id, order, date FROM test_table;
-- Error: "ORDER": invalid identifier
-- Fix 1: Use quoted identifiers for reserved words
CREATE TABLE test_table (
id NUMBER,
"order" NUMBER,
"date" VARCHAR2(10)
);
SELECT id, "order", "date" FROM test_table;
-- Fix 2: Use better column names (recommended)
CREATE TABLE test_table (
id NUMBER,
order_number NUMBER,
order_date VARCHAR2(10)
);
SELECT id, order_number, order_date FROM test_table;
-- Check if a word is reserved
SELECT * FROM v$reserved_words WHERE keyword = 'ORDER';
-- List all columns in a table
SELECT column_name, data_type, nullable
FROM user_tab_columns
WHERE table_name = 'EMPLOYEES'
ORDER BY column_id;
-- Check columns accessible to current user
SELECT owner, table_name, column_name
FROM all_tab_columns
WHERE table_name = 'EMPLOYEES'
ORDER BY owner, column_id;
-- Search for columns by pattern
SELECT table_name, column_name
FROM user_tab_columns
WHERE column_name LIKE '%NAME%'
ORDER BY table_name, column_name;
-- Check if table exists
SELECT table_name, owner
FROM all_tables
WHERE table_name = 'EMPLOYEES';
-- Check views
SELECT view_name, owner
FROM all_views
WHERE view_name = 'EMPLOYEES';
-- Check synonyms
SELECT synonym_name, table_owner, table_name
FROM all_synonyms
WHERE synonym_name = 'EMPLOYEES';
-- List Oracle reserved words
SELECT keyword, length
FROM v$reserved_words
WHERE length > 3
ORDER BY keyword;
-- Check if specific word is reserved
SELECT keyword, reserved
FROM v$reserved_words
WHERE keyword = UPPER('&word_to_check');
-- Create a function to parse and validate identifiers
CREATE OR REPLACE FUNCTION validate_identifier(p_identifier VARCHAR2)
RETURN VARCHAR2 IS
v_result VARCHAR2(100);
BEGIN
-- Check if it's a reserved word
SELECT COUNT(*) INTO v_result
FROM v$reserved_words
WHERE keyword = UPPER(p_identifier);
IF v_result > 0 THEN
RETURN 'RESERVED_WORD';
END IF;
-- Check naming rules
IF NOT REGEXP_LIKE(p_identifier, '^[A-Za-z][A-Za-z0-9_$#]*$') THEN
RETURN 'INVALID_FORMAT';
END IF;
IF LENGTH(p_identifier) > 30 THEN
RETURN 'TOO_LONG';
END IF;
RETURN 'VALID';
END;
/
-- Test identifier validity
SELECT validate_identifier('employee_id') as result FROM dual;
SELECT validate_identifier('order') as result FROM dual;
SELECT validate_identifier('123abc') as result FROM dual;
-- Create a procedure to validate column existence
CREATE OR REPLACE FUNCTION column_exists(
p_table_name VARCHAR2,
p_column_name VARCHAR2,
p_owner VARCHAR2 DEFAULT USER
) RETURN BOOLEAN IS
v_count NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM all_tab_columns
WHERE owner = UPPER(p_owner)
AND table_name = UPPER(p_table_name)
AND column_name = UPPER(p_column_name);
RETURN v_count > 0;
END;
/
-- Usage example
BEGIN
IF column_exists('EMPLOYEES', 'FIRST_NAME') THEN
DBMS_OUTPUT.PUT_LINE('Column exists');
ELSE
DBMS_OUTPUT.PUT_LINE('Column does not exist');
END IF;
END;
/
-- Create a safe query builder
CREATE OR REPLACE PACKAGE query_builder AS
TYPE string_array IS TABLE OF VARCHAR2(4000);
FUNCTION build_select(
p_table_name VARCHAR2,
p_columns string_array
) RETURN CLOB;
FUNCTION validate_columns(
p_table_name VARCHAR2,
p_columns string_array
) RETURN string_array;
END;
/
CREATE OR REPLACE PACKAGE BODY query_builder AS
FUNCTION validate_columns(
p_table_name VARCHAR2,
p_columns string_array
) RETURN string_array IS
v_invalid_columns string_array := string_array();
v_count NUMBER;
BEGIN
FOR i IN 1..p_columns.COUNT LOOP
SELECT COUNT(*)
INTO v_count
FROM user_tab_columns
WHERE table_name = UPPER(p_table_name)
AND column_name = UPPER(p_columns(i));
IF v_count = 0 THEN
v_invalid_columns.EXTEND;
v_invalid_columns(v_invalid_columns.COUNT) := p_columns(i);
END IF;
END LOOP;
RETURN v_invalid_columns;
END;
FUNCTION build_select(
p_table_name VARCHAR2,
p_columns string_array
) RETURN CLOB IS
v_sql CLOB;
v_invalid string_array;
BEGIN
-- Validate columns first
v_invalid := validate_columns(p_table_name, p_columns);
IF v_invalid.COUNT > 0 THEN
RAISE_APPLICATION_ERROR(-20001,
'Invalid columns: ' || v_invalid(1));
END IF;
-- Build SQL
v_sql := 'SELECT ';
FOR i IN 1..p_columns.COUNT LOOP
IF i > 1 THEN
v_sql := v_sql || ', ';
END IF;
v_sql := v_sql || p_columns(i);
END LOOP;
v_sql := v_sql || ' FROM ' || p_table_name;
RETURN v_sql;
END;
END;
/
-- Create a function to find columns case-insensitively
CREATE OR REPLACE FUNCTION find_column_name(
p_table_name VARCHAR2,
p_column_pattern VARCHAR2
) RETURN VARCHAR2 IS
v_column_name VARCHAR2(128);
BEGIN
SELECT column_name
INTO v_column_name
FROM user_tab_columns
WHERE table_name = UPPER(p_table_name)
AND UPPER(column_name) = UPPER(p_column_pattern);
RETURN v_column_name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Try partial match
BEGIN
SELECT column_name
INTO v_column_name
FROM user_tab_columns
WHERE table_name = UPPER(p_table_name)
AND UPPER(column_name) LIKE '%' || UPPER(p_column_pattern) || '%'
AND ROWNUM = 1;
RETURN v_column_name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;
WHEN TOO_MANY_ROWS THEN
RETURN 'AMBIGUOUS';
END;
/
-- Usage
SELECT find_column_name('EMPLOYEES', 'first_name') FROM dual;
SELECT find_column_name('EMPLOYEES', 'name') FROM dual;
-- Establish naming standards
-- Table names: UPPER_CASE with underscores
-- Column names: lower_case or snake_case
-- Aliases: short, meaningful abbreviations
-- Good examples:
SELECT
e.employee_id,
e.first_name,
e.last_name,
d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
-- Avoid reserved words as identifiers
-- Use descriptive names instead of abbreviations
-- Create a validation view for development
CREATE OR REPLACE VIEW dev_table_info AS
SELECT
t.table_name,
t.num_rows,
COUNT(c.column_name) as column_count,
LISTAGG(c.column_name, ', ') WITHIN GROUP (ORDER BY c.column_id) as columns
FROM user_tables t
LEFT JOIN user_tab_columns c ON t.table_name = c.table_name
GROUP BY t.table_name, t.num_rows
ORDER BY t.table_name;
-- Quick reference for developers
SELECT * FROM dev_table_info WHERE table_name = 'EMPLOYEES';
-- Create a checklist function for code review
CREATE OR REPLACE FUNCTION check_sql_quality(p_sql CLOB)
RETURN VARCHAR2 IS
v_issues VARCHAR2(4000) := '';
BEGIN
-- Check for common issues
IF REGEXP_COUNT(p_sql, '\*') > 0 THEN
v_issues := v_issues || 'Uses SELECT *; ';
END IF;
IF REGEXP_COUNT(p_sql, 'SELECT.*FROM.*WHERE.*=.*SELECT', 1, 1, 'i') > 0 THEN
v_issues := v_issues || 'Has subquery that might return multiple rows; ';
END IF;
-- Check for reserved words (simplified)
IF REGEXP_COUNT(p_sql, '\border\b|\bdate\b|\bsize\b', 1, 1, 'i') > 0 THEN
v_issues := v_issues || 'Contains potential reserved words; ';
END IF;
IF LENGTH(v_issues) = 0 THEN
RETURN 'No issues found';
ELSE
RETURN 'Issues: ' || v_issues;
END IF;
END;
/
// Java/Hibernate example - use proper column mapping
@Entity
@Table(name = "EMPLOYEES")
public class Employee {
@Column(name = "EMPLOYEE_ID") // Explicit column mapping
private Long employeeId;
@Column(name = "FIRST_NAME") // Avoid case sensitivity issues
private String firstName;
// Use @Column annotation for all fields to avoid issues
}
// JDBC PreparedStatement with validation
public class SafeQueryExecutor {
public boolean columnExists(Connection conn, String tableName, String columnName)
throws SQLException {
String sql = "SELECT COUNT(*) FROM user_tab_columns " +
"WHERE table_name = ? AND column_name = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, tableName.toUpperCase());
ps.setString(2, columnName.toUpperCase());
ResultSet rs = ps.executeQuery();
rs.next();
return rs.getInt(1) > 0;
}
}
}
import cx_Oracle
class SafeSQLBuilder:
def __init__(self, connection):
self.conn = connection
self.valid_columns = {}
def get_table_columns(self, table_name):
if table_name not in self.valid_columns:
cursor = self.conn.cursor()
cursor.execute("""
SELECT column_name
FROM user_tab_columns
WHERE table_name = :table_name
""", table_name=table_name.upper())
self.valid_columns[table_name] = [row[0] for row in cursor.fetchall()]
cursor.close()
return self.valid_columns[table_name]
def build_select(self, table_name, columns):
valid_columns = self.get_table_columns(table_name)
# Validate all requested columns
invalid_columns = [col for col in columns if col.upper() not in valid_columns]
if invalid_columns:
raise ValueError(f"Invalid columns: {invalid_columns}")
# Build safe SQL
column_list = ', '.join(columns)
return f"SELECT {column_list} FROM {table_name}"
  1. Use consistent naming conventions across all database objects
  2. Always qualify column names in multi-table queries
  3. Validate column existence before dynamic SQL execution
  4. Avoid using reserved words as identifiers
  5. Use proper case handling for quoted identifiers
  6. Implement code review processes to catch identifier issues
  7. Use explicit column lists instead of SELECT *
  8. Test queries with realistic data and edge cases
  1. Check column spelling and case sensitivity
  2. Verify table aliases are used consistently
  3. Confirm column exists in the referenced table
  4. Check for reserved words being used as identifiers
  5. Validate table/view permissions and existence
  6. Review subquery scope and column references
  7. Test with simplified query to isolate the issue

This error is usually easy to fix once you identify the specific identifier causing the problem and understand the rules for Oracle identifier resolution.