2026년 06월 11일 | DBMS Error 가이드
이 글에서 다루는 내용
22004 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22004 null value not allowed 는?
PostgreSQL 에러 코드 22004는 “null value not allowed” 로, NULL 값이 허용되지 않는 컨텍스트에서 NULL을 사용하려 할 때 발생합니다. 주로 함수의 파라미터, 배열 요소, 또는 특정 연산자에서 NULL 값이 명시적으로 금지된 경우 트리거됩니다. 이 에러는 NOT NULL 제약 조건 위반(에러 코드 23502)과 혼동되기 쉽지만, 22004는 데이터 값 자체의 타입 및 연산 레벨에서의 NULL 금지를 의미하는 점이 다릅니다.
주요 발생 원인
1. STRICT 함수에 NULL 인자 전달
PostgreSQL에서 STRICT 키워드로 정의된 함수는 인자 중 하나라도 NULL이면 즉시 NULL을 반환하거나 에러를 발생시킵니다. 특히 함수 내부에서 NULL을 처리하지 못하는 로직이 있을 때, 또는 함수 정의 시 NULL 입력을 명시적으로 차단한 경우 이 에러가 발생합니다. 실무에서 공통 유틸리티 함수를 만들 때 STRICT 옵션을 무심코 사용하다가 운영 중 예상치 못한 NULL 데이터가 들어와 장애가 생기는 경우가 많습니다.
2. 배열 또는 복합 타입(Composite Type) 내 NULL 불허 요소
PostgreSQL의 배열이나 복합 타입을 다룰 때, 특정 도메인(Domain) 타입이 NOT NULL 제약을 포함하고 있다면 해당 도메인 타입의 배열이나 필드에 NULL을 삽입하면 22004 에러가 발생합니다. 도메인 타입은 기본 타입에 추가 제약을 부여하는 방식으로 정의되며, 이 제약이 배열 레벨에서도 적용된다는 점을 놓치기 쉽습니다. 대규모 마이그레이션 작업이나 ETL 파이프라인에서 도메인 타입을 인지하지 못한 채 데이터를 적재할 때 빈번히 발생합니다.
3. NOT NULL 제약이 걸린 도메인(Domain) 타입 사용
사용자 정의 도메인(Domain)에 NOT NULL 제약을 선언한 뒤, 해당 도메인 타입의 컬럼이나 변수에 NULL 값을 할당하려는 경우 이 에러가 발생합니다. 일반 테이블의 NOT NULL 제약(23502)과 달리, 도메인 수준의 NULL 금지는 22004로 분류됩니다. 여러 테이블에 걸쳐 동일한 도메인 타입을 재사용하는 환경에서는 도메인 제약 변경 시 파급 효과가 크기 때문에 특히 주의가 필요합니다.
해결 방법
원인 1 해결: STRICT 함수에 NULL 인자 전달 방지
STRICT 함수를 호출하기 전에 NULL 여부를 확인하거나, 함수 정의에서 STRICT를 제거하고 내부에서 NULL을 직접 핸들링하세요.
-- 문제 상황: STRICT 함수에 NULL 전달
CREATE OR REPLACE FUNCTION divide(a NUMERIC, b NUMERIC)
RETURNS NUMERIC
LANGUAGE plpgsql
STRICT -- NULL 인자가 들어오면 에러 발생 가능
AS $$
BEGIN
RETURN a / b;
END;
$$;
-- NULL 전달 시 에러 발생
SELECT divide(10, NULL); -- 22004 에러 발생
-- 해결책 1: STRICT 제거 후 내부에서 NULL 처리
CREATE OR REPLACE FUNCTION divide_safe(a NUMERIC, b NUMERIC)
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
BEGIN
IF a IS NULL OR b IS NULL THEN
RETURN NULL; -- 또는 기본값 반환
END IF;
IF b = 0 THEN
RAISE EXCEPTION 'Division by zero';
END IF;
RETURN a / b;
END;
$$;
SELECT divide_safe(10, NULL); -- NULL 반환 (에러 없음)
-- 해결책 2: 호출 전 COALESCE로 NULL 방어
SELECT divide(COALESCE(10, 0), COALESCE(NULL, 1)); -- 정상 실행
원인 2 해결: 도메인 타입 배열의 NULL 요소 처리
도메인 타입에 NOT NULL 제약이 있다면 배열 삽입 전에 NULL 요소를 필터링하거나 기본값으로 대체해야 합니다.
-- 문제 상황: NOT NULL 도메인 타입 정의
CREATE DOMAIN positive_int AS INTEGER NOT NULL CHECK (VALUE > 0);
-- 해당 도메인 타입 배열 테이블 생성
CREATE TABLE test_domain (
id SERIAL PRIMARY KEY,
values positive_int[]
);
-- NULL 포함 배열 삽입 시 에러 발생
INSERT INTO test_domain (values) VALUES (ARRAY[1, 2, NULL]::positive_int[]);
-- ERROR: 22004: domain positive_int does not allow null values
-- 해결책 1: NULL 제거 후 삽입
INSERT INTO test_domain (values)
VALUES (ARRAY[1, 2, 3]::positive_int[]); -- NULL 제거
-- 해결책 2: 배열 삽입 전 NULL 필터링 쿼리
WITH cleaned AS (
SELECT ARRAY_REMOVE(ARRAY[1, 2, NULL, 3], NULL) AS clean_array
)
INSERT INTO test_domain (values)
SELECT clean_array::positive_int[] FROM cleaned;
-- 도메인 제약 확인 쿼리
SELECT
domain_name,
data_type,
domain_default,
is_nullable
FROM information_schema.domains
WHERE domain_schema = 'public';
원인 3 해결: 도메인 타입 NOT NULL 제약 관리
도메인의 NOT NULL 제약을 일시적으로 해제하거나, 삽입 전 데이터를 정제하는 방법을 사용합니다.
-- 문제 상황: NOT NULL 제약 도메인 타입 컬럼에 NULL 삽입
CREATE DOMAIN email_domain AS VARCHAR(255) NOT NULL
CHECK (VALUE ~* '^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$');
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email email_domain
);
-- NULL 삽입 시 22004 에러
INSERT INTO users (email) VALUES (NULL);
-- ERROR: 22004: domain email_domain does not allow null values
-- 해결책 1: 도메인 제약 수정 (신중하게!)
ALTER DOMAIN email_domain DROP NOT NULL;
-- 해결책 2: 삽입 전 NULL 체크 및 기본값 대체
INSERT INTO users (email)
SELECT COALESCE(input_email, 'unknown@example.com')::email_domain
FROM (VALUES (NULL::VARCHAR)) AS t(input_email);
-- 해결책 3: PL/pgSQL에서 예외 처리
DO $$
DECLARE
v_email email_domain;
BEGIN
BEGIN
v_email := NULL;
EXCEPTION
WHEN null_value_not_allowed THEN
RAISE NOTICE '도메인 타입에 NULL 값 할당 불가. 기본값으로 대체합니다.';
v_email := 'default@example.com';
END;
RAISE NOTICE 'Email: %', v_email;
END;
$$;
-- 현재 도메인 제약 상태 확인
SELECT
c.conname AS constraint_name,
c.contype AS constraint_type,
pg_get_constraintdef(c.oid) AS constraint_def
FROM pg_constraint c
JOIN pg_type t ON t.oid = c.contypid
WHERE t.typname = 'email_domain';
예방 방법
1. 도메인 타입 설계 시 NULL 허용 여부 명시적 문서화
도메인 타입을 생성할 때 NOT NULL 제약의 영향 범위를 팀 전체가 인지할 수 있도록 주석과 문서를 남기고, 배포 전 반드시 QA 환경에서 NULL 시나리오를 포함한 테스트를 진행하세요. 특히 도메인 타입이 여러 테이블에 걸쳐 사용된다면 변경 전 의존 관계를 반드시 확인해야 합니다.
-- 도메인 생성 시 COMMENT로 문서화
CREATE DOMAIN user_id_domain AS INTEGER NOT NULL CHECK (VALUE > 0);
COMMENT ON DOMAIN user_id_domain IS
'사용자 ID 도메인 타입. NOT NULL 제약 포함. NULL 허용 컨텍스트에서는 사용 불가.
생성일: 2024-01-01 | 담당자: DBA팀';
-- 의존 관계 사전 확인 쿼리
SELECT
n.nspname AS schema_name,
c.relname AS table_name,
a.attname AS column_name,
t.typname AS domain_name
FROM pg_attribute a
JOIN pg_class c ON c.oid = a.attrelid
JOIN pg_type t ON t.oid = a.atttypid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE t.typtype = 'd'
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY schema_name, table_name, column_name;
2. 데이터 입력 전 NULL 방어 레이어 구축
애플리케이션 레이어와 DB 레이어 양쪽에 NULL 방어 로직을 이중으로 구축하고, ETL이나 마이그레이션 스크립트에서는 항상 NULL 체크를 선행하세요. 특히 외부 소스에서 데이터를 받아 도메인 타입 컬럼에 적재하는 경우, Staging 테이블(일반 타입)을 거쳐 검증 후 최종 테이블로 이동하는 패턴을 권장합니다.
-- Staging 테이블을 활용한 안전한 데이터 적재 패턴
CREATE TABLE users_staging (
id INTEGER,
email VARCHAR(255) -- 일반 타입으로 우선 수집
);
-- 데이터 적재 후 NULL 및 유효성 검사
INSERT INTO users (email)
SELECT email::email_domain
FROM users_staging
WHERE email IS NOT NULL
AND email ~* '^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$';
-- NULL이 있는 레코드는 별도 로깅
INSERT INTO data_quality_log (source_table, issue_type, record_count, logged_at)
SELECT
'users_staging',
'NULL_EMAIL',
COUNT(*),
NOW()
FROM users_staging
WHERE email IS NULL;
관련 에러
- 23502 (not_null_violation): 테이블 컬럼의
NOT NULL제약 위반으로 발생하며, 22004와 가장 혼동되는 에러입니다. 22004는 도메인 또는 함수 파라미터 레벨, 23502는 테이블 컬럼 레벨에서 발생한다는 점이 다릅니다. - 22023 (invalid_parameter_value): 함수 파라미터에 유효하지 않은 값이 전달될 때 발생하며, NULL 관련 파라미터 검증 실패 시 22004와 함께 고려해야 합니다.
- 42804 (datatype_mismatch): 도메인 타입과 실제 데이터 타입이 맞지 않을 때 발생하며, 도메인 타입 관련 작업에서 22004와 연쇄적으로 발생할 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.