PostgreSQL 22004 오류 원인과 해결 방법 완벽 가이드

22004
2026년 10월 19일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22004 null value not allowed 는?

PostgreSQL 에러 코드 22004는 “null value not allowed” 로, NULL 값이 허용되지 않는 컨텍스트에서 NULL이 사용되거나 반환될 때 발생합니다. 주로 함수의 반환값, 배열 첨자, 또는 특정 연산자에서 NULL이 입력되거나 결과로 나왔을 때 이 에러가 트리거됩니다. 이 에러는 단순한 NOT NULL 제약 조건 위반(에러 코드 23502)과는 구분되며, 특히 내부 연산 과정에서 NULL이 허용되지 않는 위치에 놓일 때 발생하는 보다 세밀한 에러입니다.


주요 발생 원인

1. 배열 첨자(Array Subscript)로 NULL 값 사용

PostgreSQL에서 배열 요소에 접근할 때 첨자(인덱스)로 NULL을 사용하면 이 에러가 발생합니다. 배열의 인덱스는 반드시 정수값이어야 하며, NULL은 유효한 인덱스로 처리될 수 없기 때문입니다. 예를 들어 동적으로 생성된 인덱스 변수가 NULL인 상태에서 배열에 접근하려 할 때 자주 발생합니다.

2. STRICT 옵션이 적용된 함수에서 NULL 반환 또는 입력

RETURNS NULL ON NULL INPUT 또는 STRICT 키워드가 없는 사용자 정의 함수(UDF)에서, 함수 내부 로직이 NULL을 반환하거나 처리 과정에서 NULL이 허용되지 않는 인자로 전달될 때 발생합니다. 특히 PL/pgSQL 함수에서 반환 타입이 NOT NULL로 정의된 도메인(domain) 타입인 경우, 함수가 NULL을 반환하면 이 에러가 즉시 트리거됩니다.

3. NOT NULL 제약이 걸린 도메인(Domain) 타입에 NULL 할당

PostgreSQL의 도메인(Domain)은 기존 데이터 타입에 제약 조건을 추가한 사용자 정의 타입입니다. 도메인에 NOT NULL 제약이 설정되어 있는데, 해당 도메인 타입의 컬럼이나 변수에 NULL을 삽입하거나 업데이트하려 할 때 22004 에러가 발생합니다. 이는 테이블 컬럼의 NOT NULL 제약(에러 23502)과 달리, 타입 레벨에서의 NULL 금지이므로 에러 코드가 다르게 분류됩니다.


해결 방법

원인 1: 배열 첨자 NULL 문제 해결

배열 첨자로 사용하는 변수가 NULL이 아닌지 사전에 검사해야 합니다. COALESCE 함수나 IS NOT NULL 조건으로 방어 코드를 작성하세요.

-- 문제 상황: 첨자가 NULL일 때 에러 발생
DO $$
DECLARE
    arr INTEGER[] := ARRAY[10, 20, 30];
    idx INTEGER := NULL;
    val INTEGER;
BEGIN
    -- 아래 코드는 22004 에러 발생
    -- val := arr[idx];
    
    -- 해결 방법 1: NULL 체크 후 기본값 사용
    idx := COALESCE(idx, 1);
    val := arr[idx];
    RAISE NOTICE 'Value: %', val;
END;
$$;

-- 해결 방법 2: 조건문으로 NULL 방어
DO $$
DECLARE
    arr INTEGER[] := ARRAY[10, 20, 30];
    idx INTEGER := NULL;
    val INTEGER;
BEGIN
    IF idx IS NOT NULL THEN
        val := arr[idx];
        RAISE NOTICE 'Value: %', val;
    ELSE
        RAISE NOTICE 'Index is NULL, skipping array access.';
    END IF;
END;
$$;

원인 2: STRICT 함수 및 NOT NULL 도메인 반환 문제 해결

함수의 반환 타입이 NOT NULL 도메인인 경우, 함수 내부에서 NULL이 반환되지 않도록 명시적으로 처리해야 합니다.

-- 문제 상황: NOT NULL 도메인 타입 생성
CREATE DOMAIN positive_int AS INTEGER NOT NULL CHECK (VALUE > 0);

-- 문제가 되는 함수: NULL을 반환할 가능성 있음
CREATE OR REPLACE FUNCTION get_positive_value(input_val INTEGER)
RETURNS positive_int AS $$
BEGIN
    -- input_val이 NULL이거나 음수이면 에러 발생 가능
    RETURN input_val;
END;
$$ LANGUAGE plpgsql;

-- 에러 발생 케이스
-- SELECT get_positive_value(NULL); -- 22004 에러!

-- 해결 방법: 함수 내부에서 NULL 및 유효성 검사 추가
CREATE OR REPLACE FUNCTION get_positive_value_safe(input_val INTEGER)
RETURNS positive_int AS $$
BEGIN
    -- NULL이면 기본값 반환
    IF input_val IS NULL OR input_val <= 0 THEN
        RETURN 1; -- 기본값 또는 예외 처리
    END IF;
    RETURN input_val;
END;
$$ LANGUAGE plpgsql;

-- 정상 동작 확인
SELECT get_positive_value_safe(NULL);   -- 1 반환
SELECT get_positive_value_safe(5);      -- 5 반환
SELECT get_positive_value_safe(-3);     -- 1 반환 (기본값)

원인 3: 도메인 타입 NULL 제약 문제 해결

도메인에 NOT NULL 제약이 있을 때, 삽입 또는 업데이트 시 반드시 유효한 값을 제공해야 합니다.

-- 문제 상황 재현
CREATE DOMAIN email_address AS VARCHAR(255) NOT NULL 
    CHECK (VALUE ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');

CREATE TABLE users (
    id   SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email email_address  -- 도메인 타입 사용
);

-- 에러 발생: NULL 삽입 시도
-- INSERT INTO users (name, email) VALUES ('홍길동', NULL); -- 22004 에러!

-- 해결 방법 1: 항상 유효한 값 제공
INSERT INTO users (name, email) VALUES ('홍길동', 'hong@example.com');

-- 해결 방법 2: 애플리케이션 레벨에서 NULL 처리 후 삽입
-- (NULL인 경우 기본 이메일 또는 별도 처리)
INSERT INTO users (name, email)
SELECT 
    '이순신',
    COALESCE(NULL, 'default@example.com'); -- NULL을 기본값으로 대체

-- 도메인 제약 조건 확인 쿼리
SELECT 
    domain_name,
    data_type,
    domain_default,
    is_nullable
FROM information_schema.domains
WHERE domain_schema = 'public';

-- 도메인을 사용하는 컬럼 확인
SELECT 
    table_name,
    column_name,
    domain_name,
    is_nullable
FROM information_schema.columns
WHERE domain_name IS NOT NULL
  AND table_schema = 'public';

예방 방법

1. 도메인 타입 설계 시 DEFAULT 값 설정과 함수 방어 코드 작성

도메인 타입을 설계할 때 가능하면 DEFAULT 값을 설정하고, 해당 도메인 타입을 사용하는 모든 함수에서 NULL 입력에 대한 방어 코드를 반드시 포함시키세요. 특히 PL/pgSQL 함수에서는 함수 진입 시점에 인자의 NULL 여부를 먼저 체크하는 습관을 들이는 것이 중요합니다.

-- Best Practice: 도메인에 DEFAULT 설정
CREATE DOMAIN safe_text AS TEXT NOT NULL DEFAULT '';

-- Best Practice: 함수 시작 시 NULL 체크 패턴
CREATE OR REPLACE FUNCTION process_data(p_input safe_text)
RETURNS safe_text AS $$
BEGIN
    -- 입력값 방어 처리
    p_input := COALESCE(p_input, '');
    
    -- 비즈니스 로직 처리
    RETURN UPPER(TRIM(p_input));
END;
$$ LANGUAGE plpgsql;

2. 배열 연산과 동적 쿼리에서 NULL 안전 패턴 적용

배열 인덱스로 사용되는 변수나 동적으로 생성되는 값은 항상 COALESCE, NULLIF, IS NOT NULL 체크를 통해 NULL 안전(null-safe) 방식으로 처리하세요. 특히 외부 입력값(사용자 입력, API 파라미터 등)을 배열 첨자로 직접 사용하는 것은 매우 위험하므로 반드시 검증 레이어를 두어야 합니다.

-- Best Practice: NULL 안전 배열 접근 패턴
CREATE OR REPLACE FUNCTION safe_array_access(
    arr ANYARRAY,
    idx INTEGER,
    default_val ANYELEMENT DEFAULT NULL
)
RETURNS ANYELEMENT AS $$
BEGIN
    -- NULL 인덱스 방어
    IF idx IS NULL THEN
        RETURN default_val;
    END IF;
    
    -- 범위 초과 방어
    IF idx < 1 OR idx > array_length(arr, 1) THEN
        RETURN default_val;
    END IF;
    
    RETURN arr[idx];
END;
$$ LANGUAGE plpgsql;

-- 사용 예시
SELECT safe_array_access(ARRAY[100, 200, 300], NULL, 0);  -- 0 반환
SELECT safe_array_access(ARRAY[100, 200, 300], 2, 0);     -- 200 반환
SELECT safe_array_access(ARRAY[100, 200, 300], 99, 0);    -- 0 반환 (범위 초과)

관련 에러

  • 23502 (not_null_violation): 테이블 컬럼의 NOT NULL 제약 조건 위반. 22004가 타입/연산 레벨의 NULL 금지라면, 23502는 테이블 스키마 레벨의 NULL 금지입니다.
  • 22003 (numeric_value_out_of_range): 숫자 값이 허용 범위를 초과할 때 발생. 도메인의 CHECK 제약과 함께 자주 등장합니다.
  • 22P02 (invalid_text_representation): 잘못된 텍스트 표현식. 도메인 타입 변환 과정에서 NULL 관련 문제와 함께 나타날 수 있습니다.
  • 42P17 (invalid_object_definition): 도메인 정의 자체가 잘못된 경우 발생하며, NOT NULL 도메인 설계 시 주의해야 할 에러입니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기