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