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

ORA-01007
2026년 06월 27일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-01007 variable not in select list 는?

ORA-01007 에러는 Oracle에서 커서(Cursor)를 통해 데이터를 가져올 때, 참조하려는 변수(컬럼)가 해당 커서의 SELECT 목록에 존재하지 않을 경우 발생하는 에러입니다. 주로 Pro*C, OCI(Oracle Call Interface), PL/SQL 환경에서 FETCH 문을 통해 데이터를 조회할 때, INTO 절에 지정된 변수가 SELECT 리스트의 컬럼 수나 순서와 일치하지 않을 때 나타납니다. 이 에러는 런타임(Runtime) 시점에 발생하며, 컴파일 단계에서는 감지되지 않아 실무에서 디버깅에 상당한 시간이 소요될 수 있는 까다로운 에러 중 하나입니다.


주요 발생 원인

1. FETCH INTO 절의 변수 수와 SELECT 컬럼 수 불일치

가장 흔한 원인으로, 커서의 SELECT 절에 정의된 컬럼 수와 FETCH INTO 절에 나열된 변수의 수가 맞지 않을 때 발생합니다. 예를 들어, SELECT에서 3개의 컬럼을 조회하는데 INTO 절에 2개의 변수만 지정하거나, 반대로 변수가 더 많은 경우에도 이 에러가 발생합니다. 이는 개발 중 SELECT 절을 수정하면서 INTO 절을 함께 업데이트하지 않아 생기는 경우가 대부분입니다.

2. 동적 SQL(Dynamic SQL) 사용 시 컬럼 바인딩 오류

EXECUTE IMMEDIATE나 DBMS_SQL 패키지를 사용하는 동적 SQL에서 런타임에 결정되는 컬럼 목록과 변수 바인딩이 일치하지 않을 때 발생합니다. 동적 SQL은 컴파일 시점에 구조를 검증할 수 없기 때문에, 실행 시점에서야 컬럼과 변수의 불일치가 드러납니다. 특히 사용자 입력이나 파라미터에 따라 SELECT 절이 동적으로 구성되는 복잡한 쿼리에서 자주 발생합니다.

3. Pro*C 또는 OCI 프로그램에서의 호스트 변수 참조 오류

Pro*C나 OCI 기반의 C/C++ 애플리케이션에서 임베디드 SQL을 사용할 때, 호스트 변수(Host Variable)가 커서의 SELECT 리스트에 없는 컬럼을 참조하려 할 때 발생합니다. 특히 커서를 재사용하거나, 쿼리 문자열을 동적으로 변경한 후 기존 호스트 변수 구조체를 그대로 사용하는 경우에 빈번하게 나타납니다. 이 경우는 C 소스 코드와 SQL 커서 정의를 동시에 검토해야 하므로 디버깅이 특히 까다롭습니다.


해결 방법

원인 1 해결: FETCH INTO 변수 수 일치시키기

SELECT 컬럼 수와 INTO 절의 변수 수를 정확히 맞춰야 합니다.

-- 문제가 있는 코드 예시
DECLARE
  CURSOR emp_cur IS
    SELECT employee_id, first_name, last_name, salary  -- 4개 컬럼
    FROM employees
    WHERE department_id = 10;

  v_emp_id   employees.employee_id%TYPE;
  v_name     employees.first_name%TYPE;
  -- salary 변수 누락!
BEGIN
  OPEN emp_cur;
  FETCH emp_cur INTO v_emp_id, v_name;  -- ORA-01007 발생!
  CLOSE emp_cur;
END;
/

-- 수정된 코드 예시
DECLARE
  CURSOR emp_cur IS
    SELECT employee_id, first_name, last_name, salary  -- 4개 컬럼
    FROM employees
    WHERE department_id = 10;

  v_emp_id    employees.employee_id%TYPE;
  v_first_name employees.first_name%TYPE;
  v_last_name  employees.last_name%TYPE;
  v_salary     employees.salary%TYPE;
BEGIN
  OPEN emp_cur;
  LOOP
    FETCH emp_cur INTO v_emp_id, v_first_name, v_last_name, v_salary;
    EXIT WHEN emp_cur%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE('ID: ' || v_emp_id || ', Name: ' || v_first_name || ' ' || v_last_name || ', Salary: ' || v_salary);
  END LOOP;
  CLOSE emp_cur;
END;
/

더 안전한 방법으로는 커서 레코드 타입(%ROWTYPE)을 활용하면 컬럼 수 불일치 문제를 원천적으로 방지할 수 있습니다.

-- %ROWTYPE을 활용한 안전한 방법
DECLARE
  CURSOR emp_cur IS
    SELECT employee_id, first_name, last_name, salary
    FROM employees
    WHERE department_id = 10;

  v_emp_rec emp_cur%ROWTYPE;  -- 커서 구조에 맞는 레코드 타입 자동 정의
BEGIN
  OPEN emp_cur;
  LOOP
    FETCH emp_cur INTO v_emp_rec;
    EXIT WHEN emp_cur%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(
      'ID: ' || v_emp_rec.employee_id ||
      ', Name: ' || v_emp_rec.first_name || ' ' || v_emp_rec.last_name ||
      ', Salary: ' || v_emp_rec.salary
    );
  END LOOP;
  CLOSE emp_cur;
END;
/

원인 2 해결: 동적 SQL에서의 컬럼 바인딩 수정

DBMS_SQL 패키지를 사용할 때는 DEFINE_COLUMN을 SELECT 컬럼 순서대로 정확히 정의해야 합니다.

-- DBMS_SQL을 사용하는 동적 SQL 올바른 예시
DECLARE
  v_cursor   INTEGER;
  v_sql      VARCHAR2(1000);
  v_emp_id   NUMBER;
  v_name     VARCHAR2(50);
  v_salary   NUMBER;
  v_status   INTEGER;
BEGIN
  v_sql := 'SELECT employee_id, first_name, salary FROM employees WHERE department_id = :dept_id';

  v_cursor := DBMS_SQL.OPEN_CURSOR;
  DBMS_SQL.PARSE(v_cursor, v_sql, DBMS_SQL.NATIVE);

  -- 바인드 변수 설정
  DBMS_SQL.BIND_VARIABLE(v_cursor, ':dept_id', 10);

  -- SELECT 목록의 컬럼 순서와 동일하게 DEFINE_COLUMN 정의 (순서 중요!)
  DBMS_SQL.DEFINE_COLUMN(v_cursor, 1, v_emp_id);           -- 1번째 컬럼: employee_id
  DBMS_SQL.DEFINE_COLUMN(v_cursor, 2, v_name, 50);         -- 2번째 컬럼: first_name
  DBMS_SQL.DEFINE_COLUMN(v_cursor, 3, v_salary);           -- 3번째 컬럼: salary

  v_status := DBMS_SQL.EXECUTE(v_cursor);

  LOOP
    EXIT WHEN DBMS_SQL.FETCH_ROWS(v_cursor) = 0;
    DBMS_SQL.COLUMN_VALUE(v_cursor, 1, v_emp_id);
    DBMS_SQL.COLUMN_VALUE(v_cursor, 2, v_name);
    DBMS_SQL.COLUMN_VALUE(v_cursor, 3, v_salary);
    DBMS_OUTPUT.PUT_LINE('ID: ' || v_emp_id || ', Name: ' || v_name || ', Salary: ' || v_salary);
  END LOOP;

  DBMS_SQL.CLOSE_CURSOR(v_cursor);
EXCEPTION
  WHEN OTHERS THEN
    IF DBMS_SQL.IS_OPEN(v_cursor) THEN
      DBMS_SQL.CLOSE_CURSOR(v_cursor);
    END IF;
    RAISE;
END;
/

원인 3 해결: FOR 루프 커서로 단순화

Pro*C나 복잡한 커서 사용 환경에서 가능하다면 FOR 루프 커서를 활용하면 FETCH INTO 구문 자체를 제거하여 ORA-01007을 원천적으로 방지할 수 있습니다.

-- FOR 루프 커서를 활용한 가장 안전한 방법
BEGIN
  FOR emp_rec IN (
    SELECT employee_id, first_name, last_name, salary
    FROM employees
    WHERE department_id = 10
    ORDER BY employee_id
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(
      'ID: '      || emp_rec.employee_id ||
      ', Name: '  || emp_rec.first_name || ' ' || emp_rec.last_name ||
      ', Salary: ' || emp_rec.salary
    );
  END LOOP;
END;
/

예방 방법

1. 커서 레코드 타입(%ROWTYPE 또는 사용자 정의 레코드 타입) 적극 활용

FETCH INTO 절에 개별 변수를 나열하는 대신, 커서명%ROWTYPE 또는 테이블의 테이블명%ROWTYPE을 사용하면 SELECT 컬럼이 변경되어도 변수 목록을 따로 수정할 필요가 없어 유지보수성이 크게 향상됩니다. 개발 표준에 %ROWTYPE 사용을 의무화하고 코드 리뷰 시 개별 변수 나열 방식은 반드시 개선하도록 팀 내 가이드라인을 수립하세요.

2. 단위 테스트 및 코드 리뷰 프로세스 강화

SELECT 절이 변경될 때마다 INTO 절이나 DEFINE_COLUMN 정의를 함께 검토하는 체크리스트를 운영 프로세스에 포함시켜야 합니다. 특히 동적 SQL을 사용하는 코드는 다양한 파라미터 조합으로 단위 테스트를 작성하고, CI/CD 파이프라인에 통합하여 배포 전에 런타임 에러를 사전에 검출하는 체계를 갖추는 것이 중요합니다.


관련 에러

  • ORA-01008: not all variables bound – 바인드 변수가 모두 바인딩되지 않았을 때 발생하며, ORA-01007과 함께 동적 SQL 오류의 대표적인 쌍입니다.
  • ORA-01422: exact fetch returns more than requested number of rows – SELECT INTO 문에서 둘 이상의 행이 반환될 때 발생하는 에러로, 커서 관련 코드 작성 시 함께 고려해야 합니다.
  • ORA-06511: PL/SQL: cursor already open – 커서를 이미 열린 상태에서 다시 OPEN하려 할 때 발생하며, 커서 생명주기 관리 미흡으로 인해 ORA-01007과 복합적으로 나타나는 경우가 있습니다.
  • ORA-01009: missing mandatory parameter – 필수 파라미터가 누락되었을 때 발생하며, Pro*C 환경에서 ORA-01007과 유사한 맥락으로 나타날 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기