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

ORA-12015
2026년 09월 05일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-12015 cannot create a fast refresh materialized view from a complex query 는?

ORA-12015 에러는 Oracle에서 Materialized View(구체화 뷰)를 생성할 때 빠른 새로 고침(Fast Refresh) 옵션을 사용하려 했으나, 쿼리 구조가 너무 복잡하여 Oracle이 이를 지원하지 못할 때 발생합니다. Fast Refresh는 변경된 데이터만 증분 방식으로 갱신하는 효율적인 메커니즘인데, 이를 위해 Oracle은 내부적으로 Materialized View Log(MV Log)를 추적해야 하므로 지원되는 쿼리 형태에 제약이 있습니다. 주로 UNION, CONNECT BY, DISTINCT, GROUP BY와 집계 함수의 복합 사용, 서브쿼리, 분석 함수 등이 포함된 복잡한 쿼리에서 이 에러가 발생하며, 이 경우 COMPLETE REFRESH 방식으로 변경하거나 쿼리를 단순화해야 합니다.


주요 발생 원인

1. UNION / UNION ALL, CONNECT BY 등 집합 연산 사용

Fast Refresh Materialized View는 단일 기본 쿼리 구조를 기반으로 증분 변경을 추적합니다. UNION, UNION ALL, INTERSECT, MINUS, CONNECT BY와 같은 집합 연산자나 계층 쿼리를 사용하면 Oracle이 변경 로그를 단일 소스로 매핑할 수 없어 ORA-12015가 발생합니다. 이러한 연산자는 여러 결과 집합을 결합하기 때문에 MV Log 기반의 증분 추적 자체가 불가능합니다.

2. 서브쿼리 또는 분석 함수(Analytic Function) 포함

SELECT 절이나 WHERE 절에 인라인 뷰(서브쿼리), ROW_NUMBER(), RANK(), LAG(), LEAD() 등의 분석 함수가 포함되면 Fast Refresh를 지원하지 않습니다. Oracle Fast Refresh 메커니즘은 각 행의 변경을 독립적으로 추적해야 하는데, 분석 함수는 전체 결과 집합의 문맥에 따라 값이 결정되므로 증분 갱신이 불가능합니다. 특히 분석 함수는 Oracle 21c 이하 버전에서 Fast Refresh 대상에서 완전히 제외됩니다.

3. 집계 함수와 GROUP BY의 제약 조건 위반

집계 함수(SUM, AVG, COUNT 등)를 사용할 때도 특정 조건을 충족해야만 Fast Refresh가 가능합니다. 예를 들어 COUNT(*)가 없는 상태에서 AVG()를 단독 사용하거나, SELECT 절에 GROUP BY에 포함되지 않은 컬럼이 있거나, 기반 테이블에 Materialized View Log가 없는 경우 이 에러가 발생합니다. 또한 GROUP BY 없이 집계 함수만 사용하는 경우에도 Fast Refresh 조건을 충족하지 못할 수 있습니다.


해결 방법

원인 1 해결: COMPLETE REFRESH로 변경

UNION 또는 복잡한 집합 연산을 반드시 사용해야 한다면, Fast Refresh 대신 COMPLETE REFRESH로 Materialized View를 생성합니다.

-- 문제가 발생하는 쿼리 (Fast Refresh 시도)
CREATE MATERIALIZED VIEW mv_union_example
REFRESH FAST ON DEMAND
AS
SELECT emp_id, emp_name, dept_id FROM employees
UNION ALL
SELECT emp_id, emp_name, dept_id FROM contractors;
-- ORA-12015 발생!

-- 해결책: COMPLETE REFRESH로 변경
CREATE MATERIALIZED VIEW mv_union_example
REFRESH COMPLETE ON DEMAND
START WITH SYSDATE
NEXT SYSDATE + 1/24  -- 1시간마다 전체 갱신
AS
SELECT emp_id, emp_name, dept_id FROM employees
UNION ALL
SELECT emp_id, emp_name, dept_id FROM contractors;

원인 2 해결: 분석 함수를 제거하거나 별도 뷰로 분리

분석 함수가 포함된 쿼리는 먼저 일반 뷰(View) 또는 COMPLETE REFRESH MV로 처리하고, 필요 시 해당 MV 위에 별도 뷰를 생성하는 방식으로 우회합니다.

-- 문제가 발생하는 쿼리 (분석 함수 포함)
CREATE MATERIALIZED VIEW mv_rank_example
REFRESH FAST ON DEMAND
AS
SELECT
    emp_id,
    emp_name,
    salary,
    RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS salary_rank
FROM employees;
-- ORA-12015 발생!

-- 해결책 1: COMPLETE REFRESH 사용
CREATE MATERIALIZED VIEW mv_rank_example
REFRESH COMPLETE ON DEMAND
AS
SELECT
    emp_id,
    emp_name,
    salary,
    RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS salary_rank
FROM employees;

-- 해결책 2: 분석 함수 없이 Fast Refresh MV 생성 후 뷰로 래핑
CREATE MATERIALIZED VIEW mv_employees_base
REFRESH FAST ON DEMAND
AS
SELECT emp_id, emp_name, salary, dept_id FROM employees;

-- 위 MV 위에 분석 함수를 적용하는 뷰 생성
CREATE OR REPLACE VIEW v_emp_ranked AS
SELECT
    emp_id,
    emp_name,
    salary,
    RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS salary_rank
FROM mv_employees_base;

원인 3 해결: Materialized View Log 생성 및 집계 조건 충족

Fast Refresh를 집계 함수와 함께 사용하려면 반드시 기반 테이블에 MV Log를 생성하고, COUNT(*) 컬럼을 포함시켜야 합니다.

-- 1단계: 기반 테이블에 Materialized View Log 생성
CREATE MATERIALIZED VIEW LOG ON sales
WITH ROWID, SEQUENCE (sale_id, product_id, amount, sale_date)
INCLUDING NEW VALUES;

-- 2단계: Fast Refresh 조건을 충족하는 집계 MV 생성
-- 반드시 COUNT(*)를 포함해야 AVG 등이 Fast Refresh 가능
CREATE MATERIALIZED VIEW mv_sales_summary
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT
    product_id,
    COUNT(*)         AS cnt,         -- Fast Refresh 필수 항목
    SUM(amount)      AS total_amount,
    COUNT(amount)    AS cnt_amount,  -- AVG 계산용 COUNT
    MAX(sale_date)   AS last_sale_date
FROM sales
GROUP BY product_id;

-- AVG는 SUM/COUNT로 직접 계산하거나 뷰로 래핑
CREATE OR REPLACE VIEW v_sales_avg AS
SELECT
    product_id,
    total_amount / NULLIF(cnt_amount, 0) AS avg_amount,
    last_sale_date
FROM mv_sales_summary;

-- Fast Refresh 가능 여부 사전 검증 쿼리
SELECT * FROM USER_MVIEWS WHERE MVIEW_NAME = 'MV_SALES_SUMMARY';

-- MV Log 상태 확인
SELECT log_table, log_trigger, rowids, primary_key, sequence
FROM user_mview_logs
WHERE master = 'SALES';

예방 방법

1. Materialized View 설계 전 DBMS_MVIEW.EXPLAIN_MVIEW로 사전 검증하기

Materialized View를 실제로 생성하기 전에 Oracle이 제공하는 DBMS_MVIEW.EXPLAIN_MVIEW 프로시저를 활용하면 Fast Refresh 가능 여부와 그 이유를 사전에 파악할 수 있습니다. 이 프로시저는 쿼리의 어떤 부분이 Fast Refresh를 막는지 상세한 이유 코드와 메시지를 반환하므로, 설계 단계에서 미리 검증하는 습관을 들이면 실수를 크게 줄일 수 있습니다.

-- EXPLAIN_MVIEW를 위한 결과 저장 테이블 생성 (최초 1회)
-- @?/rdbms/admin/utlxmv.sql 스크립트 실행 필요

-- Fast Refresh 가능 여부 사전 확인
DECLARE
    v_query VARCHAR2(4000) := 
        'SELECT product_id, COUNT(*), SUM(amount) 
         FROM sales GROUP BY product_id';
BEGIN
    DBMS_MVIEW.EXPLAIN_MVIEW(
        mv      => v_query,
        stmt_id => 'TEST_MV_CHECK'
    );
END;
/

-- 결과 확인
SELECT capability_name, possible, msgtxt
FROM mv_capabilities_table
WHERE statement_id = 'TEST_MV_CHECK'
ORDER BY seq;

2. Fast Refresh 요건을 팀 내 개발 표준으로 문서화하기

Fast Refresh가 가능한 쿼리 패턴(단순 조인, GROUP BY + COUNT(*) 포함 집계, 단일 테이블 조회 등)과 불가능한 패턴(UNION, 서브쿼리, 분석 함수, DISTINCT 등)을 팀 개발 가이드에 명확히 정리하고, Materialized View 생성 시 코드 리뷰 체크리스트에 포함시켜야 합니다. 또한 기반 테이블 변경(컬럼 추가, 인덱스 변경) 시 연관된 MV Log와 Materialized View에 미치는 영향을 반드시 검토하는 프로세스를 수립해야 합니다.


관련 에러

  • ORA-12014: Materialized View Log에 필요한 PRIMARY KEY 정보가 없을 때 발생합니다. Fast Refresh를 위한 MV Log 설정이 불완전할 때 ORA-12015와 함께 자주 나타납니다.
  • ORA-12016: FAST REFRESH를 위한 WITH PRIMARY KEY 절이 누락되었을 때 발생하며, MV Log 구성 문제와 연관됩니다.
  • ORA-23413: 테이블에 Materialized View Log가 존재하지 않을 때 발생합니다. Fast Refresh MV 생성 전 반드시 기반 테이블의 MV Log 존재 여부를 확인해야 합니다.
  • ORA-32401: Materialized View Log가 필요한 컬럼 정보를 포함하지 않을 때 발생하며, MV Log를 재생성해야 하는 상황에서 ORA-12015와 연관되어 나타날 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기