// Developer · Debugging Reports

뉴스도 디버깅이 필요해

마주친 오류를 재현하고, 원인을 추적하고, 기록합니다. Java · Spring · DB · 실무 트러블슈팅.

debug — today.log
$ debug --trace "오늘의 뉴스"
> 헤드라인 파싱 중...
> 원인 추적: 근거 누락 · 출처 불명
> 패치 적용: 사실 확인 + 출처 보강
✓ RESOLVED
TOTAL
TODAY
YESTERDAY

Categories

웹개발/Oracle

Oracle ORA-00904 invalid identifier 오류, 컬럼명 alias 확인 순서

자바를잡아2026. 7. 14. 09:00
반응형

Oracle ORA-00904 invalid identifier 오류가 나면 먼저 SQL에 적은 컬럼명, alias, 테이블 별칭, MyBatis 동적 SQL이 실제 DB 구조와 맞는지 순서대로 확인해야 한다.

이 오류는 문법이 완전히 틀렸다기보다 Oracle이 해당 이름을 식별자로 인정하지 못할 때 자주 발생한다. 컬럼이 없거나, 별칭을 잘못 썼거나, 쌍따옴표 때문에 대소문자가 꼬였거나, Mapper XML에서 조건이 잘못 조립된 경우가 많다.

핵심 결론: ORA-00904는 Oracle이 SQL 안의 컬럼명, alias, 함수명, 식별자를 찾거나 해석하지 못했다는 신호다. 테이블 정의와 실제 실행 SQL을 함께 확인해야 한다.

확인 순서: 실제 실행 SQL 확인 → 컬럼 존재 여부 확인 → 테이블 alias 확인 → SELECT alias 사용 위치 확인 → MyBatis 동적 SQL 확인.

ORA-00904 오류의 의미

ORA-00904는 Oracle에서 invalid identifier라는 메시지와 함께 나타난다. 한국어 환경에서는 “부적합한 식별자” 또는 컬럼명이 잘못됐다는 식으로 보인다.

여기서 식별자는 컬럼명, 테이블 alias, SELECT alias, 함수명처럼 SQL 안에서 이름으로 해석되는 대상을 말한다. Oracle이 그 이름을 현재 SQL 문맥에서 찾지 못하면 ORA-00904가 발생한다.

자주 보이는 에러 형태

ORA-00904: "USER_NAME": invalid identifier

위 메시지는 USER_NAME이라는 식별자를 Oracle이 현재 SQL에서 올바르게 해석하지 못했다는 뜻이다. 실제 컬럼이 없을 수도 있고, alias가 틀렸을 수도 있고, 쌍따옴표로 생성된 컬럼의 대소문자 문제가 원인일 수도 있다.

가장 먼저 실제 실행 SQL을 확인한다

소스에 보이는 SQL과 실행 SQL은 다를 수 있다

MyBatis, Java 문자열 조립, 공통 검색 조건, 페이징 쿼리를 쓰면 소스에 보이는 SQL과 실제 Oracle에 전달되는 SQL이 다를 수 있다. 그래서 ORA-00904를 볼 때는 로그에 찍힌 최종 SQL을 먼저 확인해야 한다.

특히 MyBatis에서는 <if>, <choose>, <where> 같은 동적 태그가 조건에 따라 SQL을 바꾼다. Mapper 설정을 같이 확인할 때는 MyBatis 연동 구조를 같이 떠올리면 원인을 좁히기 쉽다.

실행 SQL을 따로 떼어 실행한다

로그에서 SQL을 확인했다면 SQL Developer, DBeaver, SQL*Plus 같은 도구에서 같은 계정으로 직접 실행해 본다.

SELECT USER_ID, USER_NAME, REG_DATE
FROM TB_USER
WHERE USE_YN = 'Y'
ORDER BY REG_DATE DESC;

직접 실행했을 때도 같은 오류가 나면 SQL 자체나 DB 구조 문제다. 애플리케이션에서만 오류가 난다면 계정, 스키마, 동적 SQL 조립, 파라미터 조건 차이를 봐야 한다.

컬럼명이 실제로 존재하는지 확인

테이블 정의부터 확인한다

가장 흔한 원인은 컬럼명이 실제 테이블에 없는 것이다. 개발자는 USER_NAME이라고 생각했지만 실제 컬럼은 USER_NM일 수 있다.

DESC TB_USER;

또는 데이터 딕셔너리에서 확인한다.

SELECT COLUMN_NAME
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = 'TB_USER'
ORDER BY COLUMN_ID;

Oracle의 일반적인 객체명은 대문자로 저장된다. 따라서 조회할 때 테이블명은 대문자로 넣는 것이 보통이다.

비슷한 컬럼명에 주의한다

잘못 쓴 이름실제 컬럼명자주 발생하는 이유
USER_NAMEUSER_NM영문 풀네임과 축약어 혼동
REG_DATEREG_DT날짜 컬럼 접미사 차이
UPDATE_IDUPD_ID프로젝트 약어 규칙 차이
DEL_YNDELETE_YN공통 컬럼명 규칙 차이

공공 SI 프로젝트에서는 테이블 설계서와 실제 DB가 조금 다를 때도 있다. 문서만 믿지 말고 실제 DB의 컬럼 목록을 확인해야 한다.

테이블 alias를 잘못 쓴 경우

alias를 붙였으면 계속 alias로 접근한다

테이블에 alias를 붙였는데 컬럼 참조에서 다른 이름을 쓰면 ORA-00904가 날 수 있다.

SELECT U.USER_ID, USER_NAME
FROM TB_USER U
WHERE TB_USER.USE_YN = 'Y';

위 SQL은 테이블 alias를 U로 줬는데 WHERE 절에서는 TB_USER.USE_YN처럼 원래 테이블명을 사용했다. alias를 붙였다면 아래처럼 통일한다.

SELECT U.USER_ID, U.USER_NAME
FROM TB_USER U
WHERE U.USE_YN = 'Y';

JOIN에서 alias가 섞이는 경우

JOIN이 많아지면 어떤 컬럼이 어느 테이블에 있는지 헷갈린다.

SELECT U.USER_ID, D.DEPT_NAME
FROM TB_USER U
JOIN TB_DEPT D ON U.DEPT_ID = D.DEPT_ID
WHERE USER_STATUS = 'A';

USER_STATUS가 어느 테이블의 컬럼인지 명확하지 않거나 실제 컬럼명이 다르면 오류가 난다. 실무에서는 JOIN SQL의 컬럼에는 alias를 붙여 읽는 편이 안전하다.

WHERE U.USER_STATUS = 'A'

SELECT alias 사용 위치 문제

WHERE 절에서는 SELECT alias를 바로 쓸 수 없다

Oracle에서는 SELECT 절에서 만든 alias를 같은 SELECT의 WHERE 절에서 바로 사용할 수 없다.

SELECT USER_ID,
       USER_NAME AS NAME
FROM TB_USER
WHERE NAME = '홍길동';

위 SQL은 NAME alias를 WHERE 절에서 사용했기 때문에 문제가 된다. WHERE 절에서는 실제 컬럼명을 써야 한다.

SELECT USER_ID,
       USER_NAME AS NAME
FROM TB_USER
WHERE USER_NAME = '홍길동';

ORDER BY에서는 alias 사용이 가능하다

반대로 ORDER BY에서는 SELECT alias를 사용할 수 있다.

SELECT USER_ID,
       USER_NAME AS NAME
FROM TB_USER
ORDER BY NAME;

이 차이 때문에 초급 개발자가 헷갈린다. WHERE는 SELECT alias 생성보다 먼저 처리되는 단계라고 보면 이해가 쉽다.

위치SELECT alias 사용비고
WHERE불가실제 컬럼명 사용
GROUP BY주의 필요표현식 기준 확인
HAVING주의 필요집계 표현식 확인
ORDER BY가능alias 정렬 가능

쌍따옴표와 대소문자 문제

Oracle 객체명은 보통 대문자로 저장된다

Oracle에서 일반적으로 따옴표 없이 만든 컬럼명은 대문자로 저장된다. 예를 들어 아래 컬럼은 실제로 USER_NAME으로 저장된다.

CREATE TABLE TB_USER (
  user_name VARCHAR2(100)
);

따라서 SQL에서 user_name, USER_NAME을 써도 보통 문제없이 동작한다.

쌍따옴표로 만든 컬럼은 대소문자를 구분한다

하지만 쌍따옴표로 컬럼을 만들면 상황이 달라진다.

CREATE TABLE TB_USER (
  "userName" VARCHAR2(100)
);

이 컬럼은 USER_NAME도 아니고 USERNAME도 아니다. 정확히 "userName"으로 참조해야 한다.

SELECT "userName"
FROM TB_USER;

실무에서는 이런 방식이 유지보수를 어렵게 만들기 때문에 일반적으로 권장하지 않는다. 외부 도구나 자동 생성 스크립트로 만들어진 테이블에서 가끔 만난다.

MyBatis에서 ORA-00904가 나는 경우

Mapper XML의 컬럼명이 DB와 다를 수 있다

MyBatis에서는 Mapper XML에 적힌 SQL이 실제 Oracle로 전달된다. Java VO 필드명과 DB 컬럼명을 섞어 쓰면 ORA-00904가 자주 난다.

<select id="selectUser" resultType="UserVO">
  SELECT userId, userName
  FROM TB_USER
</select>

위 SQL은 Java 필드명처럼 보이는 userId, userName을 컬럼명으로 쓴 예다. 실제 DB 컬럼이 USER_ID, USER_NAME이라면 SQL을 컬럼명 기준으로 작성해야 한다.

<select id="selectUser" resultType="UserVO">
  SELECT USER_ID AS userId,
         USER_NAME AS userName
  FROM TB_USER
</select>

MyBatis의 resultType, resultMap 차이를 모르면 컬럼명과 Java 프로퍼티명을 같은 것으로 착각하기 쉽다. 이 부분은 Mapper 설정을 볼 때 같이 확인하는 편이 좋다.

동적 SQL에서 조건 컬럼이 잘못 붙는 경우

검색 조건에 따라 다른 컬럼을 붙이는 동적 SQL도 주의해야 한다.

<if test="searchType == 'name'">
  AND USER_NAME LIKE '%' || #{keyword} || '%'
</if>
<if test="searchType == 'dept'">
  AND DEPT_NAME LIKE '%' || #{keyword} || '%'
</if>

DEPT_NAME이 현재 FROM 절의 테이블에 없고 JOIN도 되어 있지 않다면 ORA-00904가 발생한다. 조건이 특정 검색 유형에서만 붙기 때문에 테스트를 대충 하면 놓치기 쉽다.

JDBC와 Java 코드에서 확인할 부분

실제 SQL 문자열을 로그로 남긴다

JDBC나 StringBuilder로 SQL을 만드는 코드라면 최종 문자열을 확인해야 한다.

String sql = "SELECT USER_ID, USER_NAME " +
             "FROM TB_USER " +
             "WHERE USE_YN = ?";

log.debug("sql={}", sql);

문자열 조립 과정에서 공백이 빠지면 다른 오류가 나기도 하지만, 컬럼명이 잘못 붙어 ORA-00904로 이어질 수도 있다.

계정과 스키마 차이도 확인한다

개발 DB와 운영 DB의 테이블 구조가 다르거나, 접속 계정이 다른 스키마의 synonym을 보고 있으면 같은 SQL이 환경마다 다르게 동작할 수 있다.

SELECT OWNER, TABLE_NAME, COLUMN_NAME
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME = 'TB_USER'
  AND COLUMN_NAME = 'USER_NAME';

운영에서만 ORA-00904가 난다면 배포된 SQL만 보지 말고 접속 계정, synonym, 실제 테이블 컬럼도 확인한다.

다른 Oracle 오류와 구분하기

ORA-00001과는 원인이 다르다

ORA-00904는 컬럼명이나 식별자 문제에 가깝고, ORA-00001은 unique constraint 중복 문제다. 둘 다 저장이나 조회 중에 나타날 수 있지만 보는 위치가 다르다.

중복 키 문제라면 unique constraint 오류처럼 제약조건과 데이터 중복을 봐야 한다. ORA-00904라면 먼저 SQL의 컬럼명과 alias를 본다.

날짜 비교 오류와도 다르다

날짜 조건을 수정하다가 ORA-00904가 나는 경우도 있다. 예를 들어 날짜 컬럼명을 잘못 쓰면 ORA-00904가 나고, 날짜 문자열 형식이 잘못되면 ORA-01861 같은 오류가 날 수 있다.

날짜 조건 자체가 의심된다면 Oracle 날짜 비교 기준도 함께 확인한다. 오류 코드를 구분하면 디버깅 방향이 빨라진다.

실무에서 바로 쓰는 확인 순서

  • 로그의 실제 SQL을 먼저 본다. 소스에 보이는 SQL과 실행 SQL이 다를 수 있다.
  • DESC 또는 USER_TAB_COLUMNS로 컬럼을 확인한다. 설계서보다 실제 DB를 우선한다.
  • alias를 통일한다. 테이블 alias를 붙였으면 SELECT, WHERE, JOIN에서 같은 alias를 쓴다.
  • WHERE 절에서 SELECT alias를 쓰지 않는다. WHERE에는 실제 컬럼명이나 표현식을 사용한다.
  • MyBatis에서는 최종 SQL을 확인한다. 동적 조건이 붙은 뒤의 SQL이 중요하다.
  • 환경 차이를 본다. 운영에서만 나면 접속 계정, schema, synonym, 컬럼 버전을 확인한다.

자주 묻는 질문

  • Q. ORA-00904 invalid identifier는 무슨 뜻인가요? A. Oracle이 SQL 안의 컬럼명, alias, 식별자를 올바르게 찾거나 해석하지 못했다는 뜻입니다.
  • Q. 컬럼이 있는데도 ORA-00904가 날 수 있나요? A. 날 수 있습니다. 테이블 alias를 잘못 썼거나, 다른 스키마를 보고 있거나, 쌍따옴표 대소문자 문제가 있을 수 있습니다.
  • Q. SELECT alias를 WHERE에서 쓰면 안 되나요? A. 같은 SELECT 문의 WHERE 절에서는 SELECT alias를 바로 사용할 수 없습니다. 실제 컬럼명을 써야 합니다.
  • Q. MyBatis에서만 ORA-00904가 나는 이유는 무엇인가요? A. 동적 SQL이 조건에 따라 잘못 조립되거나 Java 필드명을 DB 컬럼명처럼 쓴 경우가 많습니다.
  • Q. 운영에서만 ORA-00904가 나면 무엇을 봐야 하나요? A. 운영 DB의 실제 컬럼, 접속 계정, synonym, 배포된 Mapper XML이 개발 환경과 같은지 확인해야 합니다.
  • Q. ORA-00904와 ORA-00001은 어떻게 다른가요? A. ORA-00904는 식별자 문제이고, ORA-00001은 unique constraint 중복 데이터 문제입니다.

정리

Oracle ORA-00904 invalid identifier 오류는 대부분 컬럼명, alias, SELECT alias 사용 위치, MyBatis 동적 SQL, 대소문자 문제에서 시작된다. 그래서 오류 메시지의 식별자만 보고 감으로 고치기보다 실제 실행 SQL과 실제 DB 컬럼 목록을 같이 확인해야 한다.

초급 개발자는 먼저 로그에 찍힌 SQL을 DB 도구에서 직접 실행하고, DESC 또는 USER_TAB_COLUMNS로 컬럼 존재 여부를 확인하는 습관을 들이는 것이 좋다. 그다음 alias, WHERE 절, Mapper XML 조건을 좁혀가면 원인을 빠르게 찾을 수 있다.

ORA-00904는 SQL이 보는 이름과 실제 DB가 가진 이름이 어긋났다는 신호다. 실행 SQL과 컬럼 목록을 나란히 놓고 확인하는 것이 가장 빠르다.
반응형