본문 바로가기
IT

ORA-01422 exact fetch returns more than requested rows — SELECT INTO 다중 행 에러 원인·해결

by 샤나엘 2026. 5. 14.
반응형

ORA-01422 exact fetch returns more than requested rows — SELECT INTO 다중 행 에러 원인·해결

ORA-01403(no data found)이 결과가 0건일 때 발생한다면, ORA-01422는 정반대 케이스다. PL/SQL의 SELECT INTO 구문이 단일 행을 기대했는데 실제로는 2행 이상이 반환될 때 발생한다. 사전 정의 예외 TOO_MANY_ROWS(SQLCODE -1422)에 매핑되어 있다.

 

본 글은 ORA-01422의 정확한 의미와 자주 발생하는 5가지 케이스, 해결 패턴 5가지(예외 처리·WHERE 조건 강화·ROWNUM·CURSOR·BULK COLLECT), ORA-01403과의 대칭 관계, 디버깅 순서까지 정리한 트러블슈팅 자료다.

 

ORA-01422

이 글의 구성

 

01에러 메시지와 카테고리
02발생 원인 5가지
03재현 시나리오와 코드 예제
04해결 패턴 5가지
05ORA-01403과의 대칭 관계
Q&A자주 묻는 질문 5가지

01 에러 메시지와 카테고리

항목 내용
에러 코드 ORA-01422
영문 메시지 exact fetch returns more than requested number of rows
SQLCODE -1422
사전 정의 예외 TOO_MANY_ROWS
Cause (공식) A SELECT INTO or RETURNING INTO statement returned more than one row.
Action (공식) 단일 행만 반환되도록 쿼리를 좁히거나 FOR LOOP·BULK COLLECT로 다중 행을 처리
발생 영역 PL/SQL SELECT INTO · RETURNING INTO · EXECUTE IMMEDIATE ... INTO

핵심 관찰 — SQL 단독은 다중 행 정상

순수 SQL SELECT는 다중 행을 그대로 반환하며 ORA-01422를 던지지 않는다. SELECT INTO·RETURNING INTO·EXECUTE IMMEDIATE ... INTO 같은 PL/SQL의 exact-fetch 구문에서만 발생한다는 점이 ORA-01403과 동일한 구조다.


02 발생 원인 5가지

원인 1 — WHERE 조건이 PK·UK 미포함

WHERE 조건이 충분히 좁지 않아 유일성이 보장되지 않을 때 발생한다. 부서명·이름·이메일처럼 중복 가능성이 있는 컬럼만 사용한 경우 흔하다.

원인 2 — JOIN으로 인한 1:N 다중 행

JOIN 결과가 의도와 달리 1:N으로 전개되면서 단일 행 기대가 깨지는 경우다. 마스터-디테일 테이블 조합에서 자주 발생한다.

원인 3 — 데이터 중복으로 가정 깨짐

평소 단일 행이라 가정한 데이터에 중복이 들어가면서 ORA-01422가 발생한다. 마이그레이션·import 직후 자주 보인다.

원인 4 — Trigger에서 다중 매칭

트리거 본문에서 마스터 테이블을 SELECT INTO 조회할 때 다중 행이 매칭되면 트리거 실행이 실패하고 원래 DML도 롤백된다.

원인 5 — PL/SQL Function 내 SELECT INTO 다중 행

Function 내부의 SELECT INTO가 다중 행을 반환하면 Function 호출 SQL 전체가 실패하면서 ORA-01422가 호출자에게 전파된다.


03 재현 시나리오와 코드 예제

원인 1·2 — 조건 불충분·JOIN 다중 행

-- ✗ 부서명만으로는 사번 1명을 보장 못 함
DECLARE
  v_id NUMBER;
BEGIN
  SELECT empno INTO v_id
    FROM emp
   WHERE dept = 'SALES';
  -- 부서에 직원 2명 이상 → ORA-01422
END;
/

-- ✓ PK 추가로 단일 행 보장
SELECT empno INTO v_id
  FROM emp
 WHERE dept = 'SALES'
   AND name = 'Alice'
   AND hire_date = DATE '2024-01-15';

원인 4 — Trigger 다중 행

CREATE OR REPLACE TRIGGER trg_check_dept
  BEFORE INSERT ON emp
  FOR EACH ROW
DECLARE
  v_dept_id NUMBER;
BEGIN
  -- ✗ 부서명 중복 가능 → ORA-01422
  SELECT id INTO v_dept_id
    FROM dept
   WHERE name = :NEW.dept_name;
END;
/

04 해결 패턴 5가지

패턴 1 — TOO_MANY_ROWS 예외 처리

BEGIN
  SELECT empno INTO v_id FROM emp WHERE dept = 'SALES';
EXCEPTION
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('중복 행 — 조건 확인 필요');
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('데이터 없음');
END;
/

패턴 2 — WHERE 조건 강화 (PK·UK 포함)

비즈니스적으로 단일 행이어야 한다면 WHERE 조건에 PK 또는 UK 컬럼을 반드시 포함시킨다. 위 원인 1·2 예시 코드의 ✓ 부분 참고.

패턴 3 — ROWNUM = 1 또는 FETCH FIRST 1 ROW ONLY

-- 다중 행 중 한 행만 받기 (순서가 중요하면 ORDER BY 필수)
SELECT empno INTO v_id FROM (
  SELECT empno FROM emp
   WHERE dept = 'SALES'
   ORDER BY hire_date DESC
)
WHERE ROWNUM = 1;

-- 12c 이상: FETCH FIRST 1 ROW ONLY (가독성 우수)
SELECT empno INTO v_id
  FROM emp
 WHERE dept = 'SALES'
 ORDER BY hire_date DESC
 FETCH FIRST 1 ROW ONLY;

주의: ORDER BY 없이 ROWNUM = 1 또는 FETCH FIRST를 사용하면 어떤 행이 나올지 비결정적이다. 비즈니스 의미가 명확한 ORDER BY 컬럼을 반드시 함께 지정한다.

패턴 4 — CURSOR + LOOP 변환

DECLARE
  CURSOR c_emp IS SELECT empno FROM emp WHERE dept = 'SALES';
BEGIN
  FOR r IN c_emp LOOP
    DBMS_OUTPUT.PUT_LINE(r.empno);
  END LOOP;
END;
/

패턴 5 — BULK COLLECT INTO

DECLARE
  TYPE t_list IS TABLE OF NUMBER;
  v_ids t_list;
BEGIN
  SELECT empno BULK COLLECT INTO v_ids
    FROM emp WHERE dept = 'SALES';
  -- 컬렉션에 모든 행 저장됨 (0건이어도 안전)
END;
/

05 ORA-01403과의 대칭 관계

에러 반환 행수 사전 정의 예외
ORA-01403 0행 (비어 있음) NO_DATA_FOUND
ORA-01422 2행 이상 (초과) TOO_MANY_ROWS

두 예외는 SELECT INTO가 "정확히 1행"을 요구한다는 동일한 전제에서 발생하는 대칭적 예외다. 두 예외를 함께 처리하는 것이 안전하다.

BEGIN
  SELECT empno INTO v_id FROM emp WHERE dept = 'SALES';
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    v_id := NULL;
  WHEN TOO_MANY_ROWS THEN
    RAISE_APPLICATION_ERROR(-20001, '다중 행 - 조건 확인');
END;
/

06 자주 묻는 질문 5가지

Q1일반 SELECT 문에서는 다중 행이 정상인데 왜 PL/SQL은 예외인가

PL/SQL의 SELECT INTO는 결과를 단일 스칼라 변수에 대입하는 구문이라 정확히 1행을 요구한다. 0행 또는 2행 이상은 모두 단일 변수 대입과 호환되지 않아 예외(각각 NO_DATA_FOUND·TOO_MANY_ROWS)를 던진다. 다중 행을 받으려면 컬렉션 변수와 BULK COLLECT INTO를 사용한다.

Q2ROWNUM = 1로 회피해도 되나

단기 회피책으로 가능하지만 어떤 행이 선택될지 비결정적이라는 점을 인지해야 한다. 비즈니스 의미가 있는 ORDER BY를 함께 지정해 어떤 행이 선택되는지 명확히 한다. 근본 해결은 WHERE 조건 강화 또는 데이터 중복 정리다.

Q3평소 잘 되던 코드가 갑자기 ORA-01422를 던진다

데이터 마이그레이션·import·신규 입력으로 인해 단일 행 가정이 깨진 경우가 흔하다. 해당 WHERE 조건의 결과 행수를 SELECT COUNT(*) 또는 그대로 SELECT 실행해 확인한다. 중복이 비즈니스적으로 정상이면 코드를 CURSOR/BULK COLLECT로 리팩토링하고, 비정상이면 데이터 정리·UK 제약을 추가한다.

Q4EXCEPTION 핸들러로만 처리해도 되나

단기 안정화에는 가능하지만 근본 해결은 아니다. 핸들러에서 단순히 NULL을 대입하거나 첫 행을 임의로 선택하면 데이터 정합성 문제가 생긴다. 가능하면 WHERE 조건 강화·중복 데이터 정리·BULK COLLECT 변환 등 구조적 해결을 우선한다.

Q5ORA-01422 발생 시 트랜잭션이 롤백되나

트리거 본문에서 ORA-01422가 발생해 트리거가 종료되면 트리거를 발화한 원래 DML이 함께 롤백된다. 일반 PL/SQL 블록에서는 예외 처리 여부에 따라 다르며, 예외를 핸들링하지 않고 종료되면 호출자까지 예외가 전파되어 명시적 COMMIT이 없으면 자동 롤백된다.


07 결론

ORA-01422는 ORA-01403의 거울상으로, "정확히 1행"이라는 SELECT INTO 전제가 깨질 때 발생한다. 디버깅 시 두 예외를 함께 떠올리고 NO_DATA_FOUND·TOO_MANY_ROWS를 동시에 처리하는 습관을 들이면 안전하다.

 

실무 적용 원칙은 다음과 같다.

 

첫째, 단일 행 SELECT INTO는 WHERE 조건에 PK·UK 컬럼을 반드시 포함시켜 유일성을 보장한다.

둘째, 다중 행 처리가 필요하면 CURSOR 또는 BULK COLLECT INTO로 구조 자체를 바꾼다.

셋째, 임시 회피로 ROWNUM·FETCH FIRST 사용 시 반드시 의미 있는 ORDER BY를 함께 지정한다.

ORA-01422는 SELECT INTO가 다중 행을 받았을 때의 예외다. WHERE 조건 강화·BULK COLLECT 전환이 근본 해결, ROWNUM은 임시 회피.

 

— NO_DATA_FOUND·TOO_MANY_ROWS는 같은 SELECT INTO에서 대칭적 예외

트러블슈팅 체크리스트

 

01문제 SELECT 문을 직접 실행해 반환 행수를 확인한다.
02WHERE 조건에 PK·UK 컬럼이 포함됐는지 점검한다.
03JOIN 결과가 의도와 같은지 SAMPLE 데이터로 검증한다.
04데이터 중복 여부를 SELECT COUNT(*) GROUP BY로 확인한다.
05다중 행이 정상이면 BULK COLLECT 또는 CURSOR로 리팩토링한다.
06NO_DATA_FOUND·TOO_MANY_ROWS 예외를 함께 처리한다.
07ROWNUM·FETCH FIRST 사용 시 반드시 의미 있는 ORDER BY를 함께 지정한다.

본 글은 Oracle Database PL/SQL 예외 ORA-01422의 일반적 원인과 해결 방법을 정리한 자료다. 운영 환경 적용 전 테스트 환경에서 충분히 검증한다.

 

#ORA01422 #TOOMANYROWS #오라클에러 #PLSQL #SELECTINTO #BULKCOLLECT #CURSOR #ROWNUM #FETCHFIRST #ORA01403 #NODATAFOUND #예외처리 #PLSQL트리거 #PLSQL함수 #DB트러블슈팅

반응형

댓글