본문 바로가기

데이터베이스/Oracle

ORA-01722 수치가 부적합합니다 원인과 해결, 문자 컬럼 비교·TO_NUMBER·VALIDATE_CONVERSION (PostgreSQL 비교)

by 아얌아얌 2026. 10. 27.

어제까지 잘 돌던 쿼리가 오늘 갑자기 ORA-01722: invalid number(수치가 부적합합니다)로 멈춘다. 쿼리는 하나도 안 바꿨는데 말이다. 이 에러는 대부분 숫자로 바꿀 수 없는 문자열이 데이터에 한 줄 들어왔을 때 터진다. 그래서 개발 DB에서는 멀쩡하고 운영 DB에서만 나는 경우가 많다.

원인 1순위

문자 컬럼을 숫자와 비교하는 암시적 형변환

결론부터 말하면, 원인 행은 VALIDATE_CONVERSION으로 찾고, 해결은 비교 대상을 문자로 맞추거나 TO_NUMBER(... DEFAULT NULL ON CONVERSION ERROR)로 막는다 (12.2부터).

재현 예제

CREATE TABLE orders (
  order_id   NUMBER,
  amount_txt VARCHAR2(20)   -- 외부 파일에서 받은 금액, 문자로 저장
);

INSERT INTO orders VALUES (1, '15000');
INSERT INTO orders VALUES (2, '23000');
INSERT INTO orders VALUES (3, '1,200');   -- 쉼표가 섞인 값
INSERT INTO orders VALUES (4, 'N/A');     -- 숫자가 아닌 값
COMMIT;

SELECT * FROM orders WHERE amount_txt > 10000;
-- ORA-01722: invalid number

amount_txt는 문자 컬럼인데 숫자 10000과 비교했다. 오라클은 이 경우 컬럼 쪽을 숫자로 바꿔서 비교한다. 그러다 '1,200'과 'N/A'에서 변환에 실패한다.

원인 4가지

1. 문자 컬럼을 숫자와 비교 (암시적 변환)

가장 흔하다. WHERE 문자컬럼 = 123처럼 따옴표 없이 숫자를 쓰면 컬럼 전체에 TO_NUMBER가 걸린 것과 같다.

2. 다른 조건으로 걸렀는데도 난다

SELECT * FROM orders
 WHERE order_id IN (1, 2)
   AND amount_txt > 10000;

1, 2번 행만 보니 괜찮을 것 같지만, 오라클은 조건을 어떤 순서로 평가할지 보장하지 않는다. 실행계획에 따라 amount_txt > 10000을 먼저 평가하면 3, 4번 행에서 에러가 난다. 그래서 "통계가 바뀌었더니 갑자기 에러"가 생긴다.

3. 소수점·천 단위 기호

'1,200', '1.234,5'처럼 기호가 섞이면 기본 TO_NUMBER로는 안 바뀐다. 세션의 NLS_NUMERIC_CHARACTERS 설정에 따라 같은 문자열이 되기도 하고 안 되기도 한다.

4. INSERT·UPDATE에서 숫자 컬럼에 문자를 넣을 때

INSERT INTO price_master (item_id, price) SELECT item_id, amount_txt FROM stage;

price가 NUMBER라면 amount_txt의 이상한 값에서 에러가 난다.

원인 행 찾기

12.2부터는 VALIDATE_CONVERSION으로 변환이 안 되는 행만 바로 골라낼 수 있다.

SELECT order_id, amount_txt
  FROM orders
 WHERE VALIDATE_CONVERSION(amount_txt AS NUMBER) = 0;
-- 3  1,200
-- 4  N/A

그 이전 버전이라면 정규식으로 찾는다.

SELECT order_id, amount_txt
  FROM orders
 WHERE NOT REGEXP_LIKE(amount_txt, '^-?[0-9]+(\.[0-9]+)?$');

해결 방법 3가지

1. 비교 대상을 문자로 맞추기 (가장 안전)

SELECT * FROM orders WHERE amount_txt = '15000';

단, 문자 비교는 크기 비교(>, <)에서 '9'가 '10000'보다 크게 나오는 식으로 의미가 달라진다. 크기 비교가 필요하면 2번이나 3번을 쓴다.

2. 변환 실패를 NULL로 (12.2+)

SELECT *
  FROM orders
 WHERE TO_NUMBER(amount_txt DEFAULT NULL ON CONVERSION ERROR) > 10000;

변환이 안 되는 행은 NULL이 되어 조건에서 빠진다. 쉼표가 있는 값은 형식 모델을 같이 준다.

TO_NUMBER(amount_txt DEFAULT NULL ON CONVERSION ERROR, '999,999,999')

3. 데이터를 고치고 컬럼 타입을 바로잡기 (근본 해결)
숫자 값이라면 처음부터 NUMBER 컬럼에 넣는 것이 맞다. 적재 단계에서 검증하고, 기존 데이터를 정리한 뒤 타입을 바꾸는 것을 권한다.

방법 장점 주의
문자로 비교 에러 원천 차단, 인덱스 그대로 사용 크기 비교 의미가 달라짐
DEFAULT NULL ON CONVERSION ERROR 쿼리만 고쳐 바로 해결 이상 데이터가 조용히 빠짐 (로그 필요)
데이터·타입 정리 근본 해결 작업량, 다른 프로그램 영향 확인

 

문자 컬럼에 WHERE 컬럼 = 123처럼 쓰면 컬럼에 함수가 걸린 것과 같아서 인덱스를 못 탈 수 있다. 에러가 안 나더라도 성능 문제의 원인이 되니, 코드 리뷰에서 암시적 형변환은 꼭 잡자.

다시 안 나게: 적재 단계에서 막기

에러를 쿼리에서 막는 것보다, 이상한 값이 처음부터 못 들어오게 하는 편이 확실하다.

-- 숫자 형식이 아니면 INSERT 자체를 거부
ALTER TABLE orders ADD CONSTRAINT chk_amount_txt
  CHECK (REGEXP_LIKE(amount_txt, '^-?[0-9]+(\.[0-9]+)?$'));

기존 데이터에 이미 이상한 값이 있으면 제약 조건이 걸리지 않는다. 먼저 VALIDATE_CONVERSION으로 찾은 행을 정리하거나, ENABLE NOVALIDATE로 새 데이터부터 검사하는 방법을 쓴다. 외부 파일을 받는 배치라면 적재 전에 형식 검사 단계를 하나 두고, 걸린 행은 별도 오류 테이블로 빼 두면 원인 추적도 쉬워진다.

PostgreSQL에서는

PostgreSQL은 문자와 숫자를 알아서 바꿔 주지 않는다. 그래서 같은 쿼리는 실행 전에 막힌다.

SELECT * FROM orders WHERE amount_txt > 10000;
-- ERROR: operator does not exist: character varying > integer

명시적으로 바꾸면 그때 데이터 문제로 에러가 난다.

SELECT * FROM orders WHERE amount_txt::numeric > 10000;
-- ERROR: invalid input syntax for type numeric: "1,200"

PostgreSQL 16부터는 pg_input_is_valid로 변환 가능한 행만 고를 수 있다.

SELECT * FROM orders
 WHERE pg_input_is_valid(amount_txt, 'numeric')
   AND amount_txt::numeric > 10000;

오라클에서 넘어올 때 암시적 형변환에 기대던 쿼리는 PostgreSQL에서 대부분 에러가 나니, 전환 전에 문자·숫자 비교를 찾아 정리해 두는 것이 좋다.

상황별로 정리하면

이런 상황이라면 가장 흔한 원인 먼저 해 볼 것
개발 DB는 괜찮고 운영에서만 난다 운영 데이터에만 이상한 값, 또는 실행계획 차이 운영 데이터를 VALIDATE_CONVERSION으로 점검
통계 수집 뒤 갑자기 난다 조건 평가 순서가 바뀜 문자 비교로 바꾸거나 안전한 TO_NUMBER 사용
뷰를 조회할 때만 난다 뷰 병합으로 바깥 조건과 섞임 뷰 안의 변환을 DEFAULT NULL ON CONVERSION ERROR로
11g라 새 함수가 없다 버전 제한 REGEXP_LIKE로 찾고 CASE WHEN으로 숫자일 때만 변환
배치 적재 중에 난다 외부 파일의 형식 오류 적재 전 형식 검사, 오류 행은 별도 테이블로

에러가 났을 때 SQL부터 고치기보다, 어떤 행이 원인인지 먼저 확인하는 습관이 시간을 가장 많이 줄여 준다. 원인 행을 보면 쉼표 문제인지, 'N/A' 같은 값인지, 빈 문자열인지가 바로 보이고 해결 방법도 그에 따라 정해진다.

함께 보면 좋은 글

참고

오라클 19c, PostgreSQL 16 기준으로 정리했다.

댓글