Oracle ORA-01422 오류 원인과 해결 방법 완벽 가이드

ORA-01422
2026년 07월 12일 | DBMS Error 가이드

이 글에서 다루는 내용

ORA-01422 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

ORA-01422 exact fetch returns more than requested number of rows 는?

ORA-01422 에러는 PL/SQL 코드에서 SELECT INTO 구문을 사용할 때, 쿼리 결과로 반환되는 행(row)의 수가 단 하나여야 하는데 실제로는 두 개 이상의 행이 반환될 경우 발생합니다. Oracle은 SELECT INTO를 통해 정확히 하나의 행만 변수에 담도록 설계되어 있으며, 이 규칙이 깨지는 순간 즉시 예외(Exception)를 발생시킵니다. 특히 WHERE 조건이 불충분하거나 데이터가 예상과 다르게 증가한 경우 실무에서 빈번하게 마주치는 에러입니다.


주요 발생 원인

  • WHERE 조건이 불충분하여 다수의 행이 반환되는 경우

가장 흔한 원인으로, SELECT INTO 구문에서 WHERE 절에 사용한 조건이 고유한 하나의 행을 특정하지 못하는 경우입니다. 예를 들어 Primary Key나 Unique 컬럼이 아닌 일반 컬럼(예: 이름, 부서명 등)으로 필터링하면 동일한 값을 가진 여러 행이 존재할 수 있습니다. 테이블에 데이터가 적을 때는 문제없이 동작하다가, 운영 환경에서 데이터가 쌓이면서 갑자기 에러가 발생하는 경우가 많아 더욱 위험합니다.

  • 데이터 무결성 문제로 중복 데이터가 존재하는 경우

Primary Key나 Unique 제약 조건이 없는 테이블, 또는 Unique 인덱스가 삭제된 테이블에서 동일한 키 값을 가진 중복 데이터가 삽입된 경우입니다. 개발 초기에는 제약 조건을 느슨하게 관리하다가 나중에 문제가 발생하는 패턴이 실무에서 자주 보입니다. 이 경우 단순히 쿼리를 수정하는 것만으로는 해결되지 않으며, 데이터 정제 및 제약 조건 재설정 작업이 병행되어야 합니다.

  • 커서(Cursor) 대신 SELECT INTO를 잘못 사용한 경우

여러 행을 처리해야 하는 비즈니스 로직임에도 불구하고, 코드 작성자가 항상 하나의 결과만 반환될 것이라고 잘못 가정하여 커서 대신 SELECT INTO를 사용한 경우입니다. 이는 설계 단계에서의 실수로, 특정 조건에서는 단일 행이 반환되지만 다른 조건에서는 복수 행이 반환될 수 있는 로직의 허점을 만들어 냅니다. 요구사항 변경이나 데이터 구조 변경 시 기존 코드를 충분히 검토하지 않아 발생하는 경우도 많습니다.


해결 방법

원인 1 해결: WHERE 조건 강화 (고유 식별자 사용)

Primary Key 또는 Unique 컬럼을 WHERE 조건에 포함하여 반드시 단일 행만 반환되도록 쿼리를 수정합니다.

-- 문제가 되는 코드 (부서명으로 조회 시 다수 행 반환 가능)
DECLARE
  v_emp_name VARCHAR2(100);
BEGIN
  SELECT emp_name
    INTO v_emp_name
    FROM employees
   WHERE dept_name = 'IT'; -- 동일 부서 직원이 여러 명일 수 있음
END;
/

-- 수정된 코드 (Primary Key인 emp_id 사용)
DECLARE
  v_emp_name VARCHAR2(100);
BEGIN
  SELECT emp_name
    INTO v_emp_name
    FROM employees
   WHERE emp_id = 1001; -- 고유 식별자로 단일 행 보장
  DBMS_OUTPUT.PUT_LINE('직원명: ' || v_emp_name);
END;
/

원인 2 해결: TOO_MANY_ROWS 예외 처리 추가

즉각적인 에러 방지를 위해 TOO_MANY_ROWS 예외를 명시적으로 처리하고, 상황에 맞는 로직을 구현합니다.

DECLARE
  v_emp_name VARCHAR2(100);
BEGIN
  SELECT emp_name
    INTO v_emp_name
    FROM employees
   WHERE dept_id = 10;

  DBMS_OUTPUT.PUT_LINE('직원명: ' || v_emp_name);

EXCEPTION
  WHEN TOO_MANY_ROWS THEN
    -- 로그 테이블에 기록하거나 별도 처리 로직 수행
    DBMS_OUTPUT.PUT_LINE('[오류] 조건에 맞는 행이 2개 이상 존재합니다. 쿼리 조건을 확인하세요.');
    -- 필요 시 에러 로깅
    INSERT INTO error_log (error_code, error_msg, log_date)
    VALUES ('ORA-01422', 'TOO_MANY_ROWS in emp query dept_id=10', SYSDATE);
    COMMIT;
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('[오류] 조건에 맞는 행이 없습니다.');
END;
/

원인 3 해결: 명시적 커서(Explicit Cursor) 또는 커서 FOR 루프 사용

복수 행을 처리해야 하는 경우, SELECT INTO 대신 커서를 사용하여 각 행을 순회 처리합니다.

-- 커서 FOR 루프를 사용한 안전한 복수 행 처리
DECLARE
  v_total_salary NUMBER := 0;
BEGIN
  FOR emp_rec IN (
    SELECT emp_id, emp_name, salary
      FROM employees
     WHERE dept_id = 10
     ORDER BY emp_id
  ) LOOP
    DBMS_OUTPUT.PUT_LINE('직원ID: ' || emp_rec.emp_id
                      || ', 이름: ' || emp_rec.emp_name
                      || ', 급여: ' || emp_rec.salary);
    v_total_salary := v_total_salary + emp_rec.salary;
  END LOOP;

  DBMS_OUTPUT.PUT_LINE('총 급여 합계: ' || v_total_salary);
END;
/

-- 명시적 커서를 사용하는 방법
DECLARE
  CURSOR emp_cursor IS
    SELECT emp_id, emp_name, salary
      FROM employees
     WHERE dept_id = 10;
  v_emp emp_cursor%ROWTYPE;
BEGIN
  OPEN emp_cursor;
  LOOP
    FETCH emp_cursor INTO v_emp;
    EXIT WHEN emp_cursor%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(v_emp.emp_name || ': ' || v_emp.salary);
  END LOOP;
  CLOSE emp_cursor;
END;
/

ROWNUM 또는 FETCH FIRST를 이용한 임시 해결책

완전한 해결이 어려운 경우 임시방편으로 첫 번째 행만 가져오는 방법도 있지만, 이는 반드시 근본 원인 분석과 병행되어야 합니다.

-- ROWNUM을 이용한 단일 행 강제 반환 (임시방편 - 권장하지 않음)
DECLARE
  v_emp_name VARCHAR2(100);
BEGIN
  SELECT emp_name
    INTO v_emp_name
    FROM employees
   WHERE dept_id = 10
     AND ROWNUM = 1; -- 첫 번째 행만 가져옴 (임시방편)
  DBMS_OUTPUT.PUT_LINE('직원명: ' || v_emp_name);
END;
/

-- Oracle 12c 이상: FETCH FIRST 사용
DECLARE
  v_emp_name VARCHAR2(100);
BEGIN
  SELECT emp_name
    INTO v_emp_name
    FROM employees
   WHERE dept_id = 10
   FETCH FIRST 1 ROWS ONLY;
  DBMS_OUTPUT.PUT_LINE('직원명: ' || v_emp_name);
END;
/

예방 방법

  • 코드 리뷰 시 SELECT INTO 사용 패턴 반드시 검증

SELECT INTO를 사용하는 모든 PL/SQL 코드에서 WHERE 조건이 Primary Key, Unique Key 또는 그에 준하는 고유 식별자를 포함하는지 코드 리뷰 체크리스트에 항목으로 추가하세요. 또한 TOO_MANY_ROWSNO_DATA_FOUND 예외 처리가 항상 함께 구현되도록 팀 내 코딩 가이드라인으로 표준화하는 것이 중요합니다. 정적 분석 도구(예: PL/SQL Analyzer, SonarQube for PL/SQL)를 CI/CD 파이프라인에 통합하면 배포 전에 위험 패턴을 자동으로 감지할 수 있습니다.

  • 테이블 설계 단계에서 적절한 제약 조건 설정

데이터 무결성을 보장하기 위해 테이블 설계 시 반드시 Primary Key, Unique Constraint, Unique Index를 적절히 설정하세요. 제약 조건이 누락된 기존 테이블에 대해서는 정기적인 데이터 품질 감사(Data Quality Audit)를 통해 중복 데이터를 사전에 발견하고 제거해야 합니다. 아래와 같이 중복 데이터를 사전에 탐지하는 쿼리를 주기적으로 실행하는 것을 권장합니다.

-- 중복 데이터 사전 탐지 쿼리 (예방적 모니터링)
SELECT dept_id,
       COUNT(*) AS row_count
  FROM employees
 GROUP BY dept_id
HAVING COUNT(*) > 1
 ORDER BY row_count DESC;

-- 중복 행 상세 조회
SELECT emp_id, emp_name, dept_id,
       ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY emp_id) AS rn
  FROM employees
 ORDER BY dept_id, emp_id;

관련 에러

  • ORA-01403: no data foundSELECT INTO 구문에서 반환되는 행이 하나도 없을 때 발생하는 에러로, ORA-01422와 쌍을 이루는 반대 상황입니다. 두 예외를 항상 함께 처리하는 습관을 들이세요.
  • ORA-06512 — PL/SQL 코드 내에서 처리되지 않은 예외가 발생했을 때 스택 트레이스와 함께 나타나는 에러로, ORA-01422 발생 시 함께 로그에 기록되는 경우가 많습니다.
  • ORA-02292: integrity constraint violated – child record found — 데이터 무결성 제약 조건 관련 에러로, 제약 조건 미설정으로 인한 중복 데이터 문제와 연관성이 있습니다.

DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기