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

ORA-02251
2026년 08월 14일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-02251 subquery not allowed here 는?

ORA-02251 오류는 Oracle SQL에서 서브쿼리(Subquery)가 허용되지 않는 위치에 서브쿼리를 사용했을 때 발생하는 에러입니다. 주로 CHECK 제약 조건(Constraint), DEFAULT 절, GROUP BY 절, ORDER BY 절 내부, 또는 특정 DDL 문장 안에서 서브쿼리를 사용하려 할 때 Oracle 파서가 이를 거부하며 발생합니다. 30년간 현장에서 수없이 접해온 이 에러는 SQL 문법 규칙을 정확히 이해하지 못한 상태에서 다른 DBMS의 습관을 그대로 Oracle에 적용하려 할 때 특히 자주 발생합니다.


주요 발생 원인

1. CHECK 제약 조건(CHECK Constraint) 내에서 서브쿼리 사용

Oracle의 CHECK 제약 조건은 정적인 조건식만 허용하며, 다른 테이블을 참조하는 서브쿼리나 심지어 같은 테이블의 다른 행을 조회하는 서브쿼리도 허용하지 않습니다. 이는 Oracle의 설계 철학으로, CHECK 제약 조건은 반드시 해당 행(Row) 단위의 컬럼 값만을 기반으로 검증해야 한다는 원칙 때문입니다. 많은 개발자들이 무결성 검증 로직을 CHECK 안에 서브쿼리로 구현하려다 이 에러를 만납니다.

2. DEFAULT 절에서 서브쿼리 사용 (Oracle 11g 이하)

테이블 생성(CREATE TABLE) 또는 컬럼 추가(ALTER TABLE ADD COLUMN) 시 DEFAULT 값으로 서브쿼리를 지정하려 할 때 발생합니다. Oracle 11g Release 2 이하 버전에서는 DEFAULT 절에 서브쿼리를 전혀 사용할 수 없었으며, Oracle 12c부터 제한적으로 허용되기 시작했습니다. 버전을 고려하지 않고 코드를 작성하거나 마이그레이션 작업 시 이 에러가 빈번하게 발생합니다.

3. DDL 문장(CREATE VIEW, CREATE TABLE AS SELECT 등)의 제약 조건 절에서 서브쿼리 사용

VIEW 생성 시 WITH CHECK OPTION 절이나 테이블 생성 시 컬럼 레벨 제약 조건 정의 부분에서 서브쿼리를 사용하려 할 때 발생합니다. 특히 복잡한 뷰(View)를 생성하거나 파티션 테이블을 정의하는 과정에서 의도치 않게 허용되지 않는 위치에 서브쿼리를 삽입하는 경우가 많습니다. DDL과 DML의 서브쿼리 허용 범위가 다르다는 점을 명확히 인지해야 합니다.


해결 방법

원인 1 해결: CHECK 제약 조건 대신 트리거(Trigger) 또는 참조 무결성 사용

CHECK 제약 조건 내에서 서브쿼리가 필요한 경우, BEFORE INSERT OR UPDATE 트리거를 활용하여 동일한 로직을 구현합니다.

❌ 잘못된 예제 (ORA-02251 발생):

-- 다른 테이블의 값을 참조하는 CHECK 제약 조건 - 에러 발생
CREATE TABLE orders (
    order_id    NUMBER PRIMARY KEY,
    customer_id NUMBER,
    amount      NUMBER,
    CONSTRAINT chk_valid_customer 
        CHECK (customer_id IN (SELECT customer_id FROM customers))  -- ORA-02251 발생!
);

✅ 올바른 예제 1 – FOREIGN KEY 사용 (참조 무결성):

-- 다른 테이블의 값 검증은 FOREIGN KEY 제약 조건으로 처리
CREATE TABLE customers (
    customer_id NUMBER PRIMARY KEY,
    customer_nm VARCHAR2(100)
);

CREATE TABLE orders (
    order_id    NUMBER PRIMARY KEY,
    customer_id NUMBER,
    amount      NUMBER,
    CONSTRAINT fk_orders_customer 
        FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

✅ 올바른 예제 2 – TRIGGER 사용 (복잡한 비즈니스 로직):

-- 복잡한 조건 검증은 트리거로 구현
CREATE OR REPLACE TRIGGER trg_orders_check_customer
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW
DECLARE
    v_count NUMBER;
BEGIN
    SELECT COUNT(*)
    INTO   v_count
    FROM   customers
    WHERE  customer_id = :NEW.customer_id
    AND    status      = 'ACTIVE';  -- 활성 고객만 허용하는 복잡한 조건

    IF v_count = 0 THEN
        RAISE_APPLICATION_ERROR(-20001, 
            '유효하지 않은 고객 ID이거나 비활성 고객입니다: ' || :NEW.customer_id);
    END IF;
END;
/

원인 2 해결: DEFAULT 절 서브쿼리는 버전에 맞게 대체

❌ 잘못된 예제 (Oracle 11g 이하에서 ORA-02251 발생):

-- Oracle 11g 이하에서는 DEFAULT에 서브쿼리 불가
CREATE TABLE employee_log (
    log_id      NUMBER PRIMARY KEY,
    emp_id      NUMBER,
    dept_id     NUMBER DEFAULT (SELECT dept_id FROM departments WHERE dept_nm = 'DEFAULT_DEPT'),  -- 에러!
    log_date    DATE DEFAULT SYSDATE
);

✅ 올바른 예제 1 – 트리거로 DEFAULT 값 설정:

-- 트리거를 이용한 DEFAULT 값 자동 설정
CREATE TABLE employee_log (
    log_id   NUMBER PRIMARY KEY,
    emp_id   NUMBER,
    dept_id  NUMBER,
    log_date DATE DEFAULT SYSDATE
);

CREATE OR REPLACE TRIGGER trg_employee_log_default
BEFORE INSERT ON employee_log
FOR EACH ROW
BEGIN
    IF :NEW.dept_id IS NULL THEN
        SELECT dept_id
        INTO   :NEW.dept_id
        FROM   departments
        WHERE  dept_nm = 'DEFAULT_DEPT'
        AND    ROWNUM  = 1;
    END IF;
END;
/

✅ 올바른 예제 2 – INSERT 시 명시적으로 서브쿼리 사용:

-- INSERT 문 자체에서 서브쿼리로 기본값 지정
INSERT INTO employee_log (log_id, emp_id, dept_id, log_date)
SELECT 
    seq_employee_log.NEXTVAL,
    100,
    (SELECT dept_id FROM departments WHERE dept_nm = 'DEFAULT_DEPT' AND ROWNUM = 1),
    SYSDATE
FROM DUAL;

원인 3 해결: DDL 제약 조건 절에서의 서브쿼리 제거

❌ 잘못된 예제:

-- VIEW의 WITH CHECK OPTION과 함께 서브쿼리를 잘못 사용한 경우
CREATE OR REPLACE VIEW vw_active_orders AS
SELECT *
FROM   orders
WHERE  order_id IN (SELECT order_id FROM order_status WHERE status = 'ACTIVE')
WITH CHECK OPTION CONSTRAINT chk_active_only;
-- 특정 상황에서 서브쿼리와 WITH CHECK OPTION 조합이 ORA-02251을 유발할 수 있음

✅ 올바른 예제 – 조인(JOIN)으로 대체:

-- 서브쿼리 대신 JOIN을 사용하여 동일한 결과 구현
CREATE OR REPLACE VIEW vw_active_orders AS
SELECT o.*
FROM   orders       o
JOIN   order_status os ON o.order_id = os.order_id
WHERE  os.status = 'ACTIVE';

-- 또는 인라인 뷰(Inline View)로 분리하여 처리
CREATE OR REPLACE VIEW vw_active_orders AS
SELECT o.*
FROM   orders o
WHERE  EXISTS (
    SELECT 1
    FROM   order_status os
    WHERE  os.order_id = o.order_id
    AND    os.status   = 'ACTIVE'
);

예방 방법

1. CHECK 제약 조건의 사용 범위를 명확히 문서화하고 코드 리뷰에 반영하기

팀 내 개발 표준(Coding Standard) 문서에 “CHECK 제약 조건에는 서브쿼리 사용 불가, 타 테이블 참조 시 반드시 FOREIGN KEY 또는 TRIGGER를 사용한다”는 규칙을 명시하고, 코드 리뷰(Code Review) 단계에서 DDL 문장 내 제약 조건 정의를 반드시 점검하는 프로세스를 수립하세요. 특히 신규 개발자나 타 DBMS 경력자가 합류할 때 이 차이점을 반드시 온보딩 교육에 포함시켜야 합니다.

2. 개발 환경에서 Oracle 버전별 SQL 문법 호환성 사전 검증 수행

운영 환경과 동일한 Oracle 버전의 개발/테스트 환경을 유지하고, DDL 스크립트를 운영 반영 전에 반드시 해당 버전에서 사전 실행 테스트를 수행하세요. SQL*Plus 또는 SQL Developer의 스크립트 실행 로그를 저장하고, CI/CD 파이프라인에 DDL 검증 단계를 포함시키면 운영 장애로 이어지는 ORA-02251 에러를 사전에 차단할 수 있습니다.


관련 에러

  • ORA-02290: CHECK constraint violated — CHECK 제약 조건 자체는 올바르게 정의되었으나 데이터 입력 시 조건을 위반했을 때 발생하며, ORA-02251과는 발생 시점(DDL vs DML)이 다릅니다.
  • ORA-02436: date or system variable wrongly specified in CHECK constraint — CHECK 제약 조건 내에서 SYSDATE, USER 등 동적 함수를 사용할 때 발생하는 유사 에러입니다.
  • ORA-00936: missing expression — 서브쿼리 구문이 불완전하거나 잘못된 위치에 사용될 때 함께 발생할 수 있습니다.
  • ORA-01427: single-row subquery returns more than one row — 서브쿼리 위치 문제를 해결한 후 서브쿼리가 여러 행을 반환할 때 연이어 만날 수 있는 에러입니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기