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

ORA-01776
2026년 07월 31일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-01776 cannot modify more than one base table through a join view 는?

ORA-01776 에러는 조인 뷰(Join View)를 통해 DML(INSERT, UPDATE, DELETE) 작업을 수행할 때, 하나의 DML 문장이 두 개 이상의 기본 테이블(Base Table)을 동시에 수정하려 할 때 발생합니다. Oracle은 조인 뷰를 통한 데이터 변경 시 단 하나의 기본 테이블만 수정할 수 있도록 제한하고 있으며, 이 규칙을 위반하면 즉시 해당 에러를 반환합니다. 특히 두 개 이상의 테이블을 JOIN한 뷰에 대해 UPDATE나 DELETE를 시도할 때 가장 빈번하게 발생하며, 초보 개발자와 경력자 모두 예상치 못한 상황에서 마주치는 대표적인 Oracle 에러 중 하나입니다.


주요 발생 원인

1. 조인 뷰에서 여러 기본 테이블의 컬럼을 동시에 UPDATE하는 경우

가장 흔한 원인으로, 두 개 이상의 테이블을 JOIN한 뷰를 정의한 후 SET 절에서 서로 다른 기본 테이블에 속한 컬럼들을 한 번에 수정하려 할 때 발생합니다. Oracle 엔진은 단일 DML 구문으로 복수의 기본 테이블을 동시에 변경하는 것을 허용하지 않으므로, 어느 테이블을 먼저 수정해야 할지 결정할 수 없어 에러를 반환합니다. 예를 들어 EMPLOYEES와 DEPARTMENTS 테이블을 조인한 뷰에서 직원 이름과 부서 이름을 동시에 바꾸려는 경우가 대표적입니다.

2. Key-Preserved Table이 아닌 테이블의 컬럼을 수정하려는 경우

조인 뷰에서 DML이 가능하려면 수정 대상 컬럼이 반드시 Key-Preserved Table에 속해야 합니다. Key-Preserved Table이란 조인 결과에서 해당 테이블의 Primary Key 또는 Unique Key가 유일성을 보장하는 테이블을 의미합니다. Key-Preserved 조건을 만족하지 않는 테이블의 컬럼을 수정하려 하면, 데이터 무결성을 보장할 수 없기 때문에 Oracle이 ORA-01776 혹은 ORA-01779 에러를 발생시킵니다.

3. INSTEAD OF 트리거 없이 복잡한 조인 뷰에 INSERT/DELETE를 시도하는 경우

두 테이블 이상을 조인한 뷰에 INSTEAD OF 트리거를 정의하지 않은 상태에서 INSERT나 DELETE를 수행하려 할 때도 이 에러가 발생할 수 있습니다. Oracle은 조인 뷰를 통한 INSERT의 경우 Key-Preserved Table이 단 하나여야 하며, DELETE의 경우에도 단일 기본 테이블에만 영향을 줄 수 있습니다. 복잡한 비즈니스 로직이 포함된 뷰에서 직접 DML을 시도하는 것은 매우 위험하며, 반드시 INSTEAD OF 트리거나 저장 프로시저를 통한 처리를 고려해야 합니다.


해결 방법

원인 1 해결: 각 기본 테이블을 개별적으로 UPDATE

조인 뷰에서 복수의 테이블 컬럼을 한 번에 수정하는 대신, 각 기본 테이블에 대해 별도의 UPDATE 문을 사용합니다.

-- 문제가 되는 코드 (ORA-01776 발생)
-- 조인 뷰 생성
CREATE OR REPLACE VIEW emp_dept_view AS
SELECT e.employee_id,
       e.first_name,
       e.last_name,
       e.salary,
       d.department_id,
       d.department_name
FROM   employees e
JOIN   departments d ON e.department_id = d.department_id;

-- 아래 UPDATE는 두 기본 테이블(employees, departments)을 동시에 수정하므로 에러 발생
UPDATE emp_dept_view
SET    salary          = 5000,      -- employees 테이블 컬럼
       department_name = 'IT_NEW'   -- departments 테이블 컬럼
WHERE  employee_id = 100;
-- ORA-01776: cannot modify more than one base table through a join view

-- ✅ 해결 방법: 각 테이블을 개별 UPDATE로 분리
UPDATE employees
SET    salary = 5000
WHERE  employee_id = 100;

UPDATE departments
SET    department_name = 'IT_NEW'
WHERE  department_id = (
    SELECT department_id
    FROM   employees
    WHERE  employee_id = 100
);

COMMIT;

원인 2 해결: Key-Preserved Table 확인 후 단일 테이블만 수정

USER_UPDATABLE_COLUMNS 뷰를 조회하여 어떤 컬럼이 수정 가능한지 먼저 확인합니다.

-- 수정 가능한 컬럼 목록 확인
SELECT column_name,
       updatable,
       insertable,
       deletable
FROM   user_updatable_columns
WHERE  table_name = 'EMP_DEPT_VIEW'
ORDER BY column_name;

-- 결과 예시:
-- COLUMN_NAME      UPDATABLE  INSERTABLE  DELETABLE
-- DEPARTMENT_ID    NO         NO          NO
-- DEPARTMENT_NAME  NO         NO          NO
-- EMPLOYEE_ID      YES        YES         YES
-- FIRST_NAME       YES        YES         YES
-- LAST_NAME        YES        YES         YES
-- SALARY           YES        YES         YES

-- ✅ Key-Preserved인 employees 테이블 컬럼만 뷰를 통해 수정 가능
UPDATE emp_dept_view
SET    salary     = 6000,
       first_name = 'John'
WHERE  employee_id = 100;

COMMIT;

원인 3 해결: INSTEAD OF 트리거 사용

복잡한 조인 뷰에서 DML을 허용하려면 INSTEAD OF 트리거를 정의하여 각 기본 테이블에 대한 처리를 명시적으로 구현합니다.

-- INSTEAD OF 트리거 생성으로 조인 뷰 DML 허용
CREATE OR REPLACE TRIGGER trg_emp_dept_view_upd
INSTEAD OF UPDATE ON emp_dept_view
FOR EACH ROW
BEGIN
    -- employees 테이블 업데이트
    IF :NEW.salary     != :OLD.salary
    OR :NEW.first_name != :OLD.first_name
    OR :NEW.last_name  != :OLD.last_name THEN
        UPDATE employees
        SET    salary     = :NEW.salary,
               first_name = :NEW.first_name,
               last_name  = :NEW.last_name
        WHERE  employee_id = :OLD.employee_id;
    END IF;

    -- departments 테이블 업데이트
    IF :NEW.department_name != :OLD.department_name THEN
        UPDATE departments
        SET    department_name = :NEW.department_name
        WHERE  department_id   = :OLD.department_id;
    END IF;
END;
/

-- 이제 아래 구문 정상 실행 가능
UPDATE emp_dept_view
SET    salary          = 7000,
       department_name = 'FINANCE_NEW'
WHERE  employee_id = 100;

COMMIT;

추가 해결책: 저장 프로시저 활용

뷰를 통한 DML 대신 저장 프로시저에서 각 테이블을 명시적으로 처리하는 방법도 안전하고 유지보수가 용이합니다.

CREATE OR REPLACE PROCEDURE update_emp_dept (
    p_employee_id   IN employees.employee_id%TYPE,
    p_salary        IN employees.salary%TYPE,
    p_dept_name     IN departments.department_name%TYPE
)
AS
    v_dept_id departments.department_id%TYPE;
BEGIN
    -- 직원의 부서 ID 조회
    SELECT department_id
    INTO   v_dept_id
    FROM   employees
    WHERE  employee_id = p_employee_id;

    -- employees 테이블 업데이트
    UPDATE employees
    SET    salary = p_salary
    WHERE  employee_id = p_employee_id;

    -- departments 테이블 업데이트
    UPDATE departments
    SET    department_name = p_dept_name
    WHERE  department_id   = v_dept_id;

    COMMIT;
    DBMS_OUTPUT.PUT_LINE('정상 처리 완료: employee_id=' || p_employee_id);
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('에러: 해당 직원을 찾을 수 없습니다.');
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END update_emp_dept;
/

-- 프로시저 실행
EXEC update_emp_dept(100, 8000, 'FINANCE_UPDATED');

예방 방법

1. 뷰 설계 시 DML 가능 여부를 사전에 명확히 정의하고 문서화

조인 뷰를 설계할 때부터 해당 뷰가 조회 전용인지, DML을 허용해야 하는지를 명확히 결정해야 합니다. DML이 필요한 경우에는 반드시 INSTEAD OF 트리거를 함께 설계하거나, 뷰 대신 저장 프로시저/패키지로 비즈니스 로직을 캡슐화하는 방향을 선택합니다. 개발 초기 단계에서 USER_UPDATABLE_COLUMNS를 통해 각 뷰의 컬럼별 DML 가능 여부를 검토하고 이를 설계 문서에 포함시키는 습관을 들이면, 운영 중 예상치 못한 에러를 크게 줄일 수 있습니다.

2. WITH CHECK OPTION 및 WITH READ ONLY 옵션을 적극 활용

조회 전용으로만 사용할 뷰에는 WITH READ ONLY 옵션을 반드시 추가하여 의도치 않은 DML 시도 자체를 차단합니다. 또한 단일 테이블 뷰이더라도 WHERE 조건이 있는 경우 WITH CHECK OPTION을 사용하면 뷰의 조건을 벗어나는 데이터 변경을 방지할 수 있습니다. 이러한 옵션들을 표준화하면 개발팀 전체가 뷰의 사용 목적을 명확히 이해하고, 잘못된 DML로 인한 데이터 무결성 문제를 사전에 예방할 수 있습니다.


관련 에러

  • ORA-01779: cannot modify a column which maps to a non key-preserved table — 조인 뷰에서 Key-Preserved 조건을 충족하지 않는 테이블의 컬럼을 수정하려 할 때 발생하며, ORA-01776과 함께 가장 자주 나타나는 뷰 관련 에러입니다.
  • ORA-01732: data manipulation operation not legal on this view — 뷰 자체의 구조(GROUP BY, DISTINCT, 집계 함수 등)로 인해 DML이 불가능한 경우 발생합니다.
  • ORA-01733: virtual column not allowed here — 뷰의 가상 컬럼(수식이나 함수로 계산된 컬럼)에 INSERT 또는 UPDATE를 시도할 때 발생합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기