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 error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.