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

22004
2026년 08월 15일 | DBMS Error 가이드

이 글에서 다루는 내용

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

22004 null value not allowed 는?

PostgreSQL 에러 코드 22004null value not allowed 에러로, NULL 값이 허용되지 않는 컬럼이나 파라미터에 NULL을 삽입하거나 전달하려 할 때 발생합니다. 이 에러는 주로 NOT NULL 제약 조건이 걸린 컬럼에 NULL을 넣으려 하거나, 함수 또는 프로시저의 파라미터가 NULL을 허용하지 않도록 선언된 경우에 나타납니다. 데이터 무결성을 지키기 위한 PostgreSQL의 방어 메커니즘이지만, 예상치 못한 데이터 흐름에서 갑작스럽게 발생하면 서비스 장애로 이어질 수 있어 반드시 원인을 파악하고 신속히 대응해야 합니다.


주요 발생 원인

1. NOT NULL 제약 조건이 있는 컬럼에 NULL 삽입 시도

가장 흔한 원인으로, 테이블 정의 시 NOT NULL 제약 조건이 걸려 있는 컬럼에 명시적으로 NULL을 삽입하거나, INSERT/UPDATE 시 해당 컬럼을 누락하여 묵시적으로 NULL이 전달되는 경우입니다. 특히 애플리케이션 레벨에서 유효성 검사 없이 DB에 바로 데이터를 넣는 구조에서 자주 발생하며, 대량 데이터 마이그레이션 작업 중에도 흔히 마주치는 상황입니다.

2. 함수 또는 프로시저의 파라미터에 NULL 전달

PostgreSQL의 사용자 정의 함수(UDF)나 저장 프로시저에서 특정 파라미터가 NOT NULL로 선언되어 있거나, 함수 내부 로직에서 NULL 체크 없이 NULL을 처리하려 할 때 발생합니다. 특히 함수 내에서 STRICT 옵션을 사용하는 경우, 어떤 파라미터라도 NULL이 전달되면 함수 실행 자체가 즉시 중단되고 NULL을 반환하거나 에러를 유발하게 됩니다.

3. 도메인(Domain) 또는 복합 타입에 정의된 NOT NULL 제약 위반

PostgreSQL은 사용자 정의 도메인(Domain) 타입에 NOT NULL 제약을 걸 수 있으며, 해당 도메인 타입을 사용하는 컬럼에 NULL을 삽입할 경우 이 에러가 발생합니다. 단순한 컬럼 제약이 아닌 타입 수준에서의 제약이기 때문에, 스키마를 새로 접하는 개발자나 DBA가 원인을 파악하는 데 더 오랜 시간이 걸리는 경우가 많습니다.


해결 방법

원인 1 해결: NOT NULL 컬럼에 기본값 또는 유효한 값 지정

먼저 문제가 되는 컬럼과 제약 조건을 확인합니다.

-- 테이블의 컬럼 제약 조건 확인
SELECT
    column_name,
    data_type,
    is_nullable,
    column_default
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name = 'orders';

NULL이 들어오는 상황이라면, INSERT 시 기본값을 명시하거나 DEFAULT를 설정하여 해결합니다.

-- 문제가 되는 INSERT (NULL 삽입 시도)
INSERT INTO orders (order_id, customer_id, status)
VALUES (1001, NULL, 'pending');
-- ERROR: null value in column "customer_id" violates not-null constraint

-- 해결책 1: 유효한 값으로 대체
INSERT INTO orders (order_id, customer_id, status)
VALUES (1001, 0, 'pending'); -- 0을 "미지정 고객"으로 처리

-- 해결책 2: 컬럼에 DEFAULT 값 추가 (스키마 변경)
ALTER TABLE orders
    ALTER COLUMN customer_id SET DEFAULT 0;

-- 해결책 3: NULL을 허용해야 하는 경우 제약 조건 제거
ALTER TABLE orders
    ALTER COLUMN customer_id DROP NOT NULL;

대량 데이터 처리 시 NULL 값을 사전에 걸러내는 방법도 유효합니다.

-- 마이그레이션 전 NULL 데이터 점검 쿼리
SELECT COUNT(*) AS null_count
FROM source_table
WHERE customer_id IS NULL;

-- COALESCE를 활용하여 NULL을 기본값으로 치환 후 INSERT
INSERT INTO orders (order_id, customer_id, status)
SELECT
    order_id,
    COALESCE(customer_id, 0) AS customer_id,
    COALESCE(status, 'unknown') AS status
FROM staging_orders;

원인 2 해결: 함수 파라미터 NULL 처리

STRICT 함수에서의 NULL 처리 방법을 확인합니다.

-- 문제가 되는 STRICT 함수 예시
CREATE OR REPLACE FUNCTION get_discount(price NUMERIC, rate NUMERIC)
RETURNS NUMERIC
LANGUAGE plpgsql
STRICT  -- 모든 파라미터가 NULL이면 즉시 NULL 반환
AS $$
BEGIN
    RETURN price * rate;
END;
$$;

-- NULL 전달 시 STRICT 함수는 NULL 반환 (에러 없이 처리)
SELECT get_discount(100, NULL); -- 결과: NULL

-- 해결책: STRICT 제거 후 내부에서 명시적 NULL 핸들링
CREATE OR REPLACE FUNCTION get_discount_safe(price NUMERIC, rate NUMERIC)
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
BEGIN
    IF price IS NULL OR rate IS NULL THEN
        RAISE EXCEPTION 'price와 rate는 NULL일 수 없습니다. (22004)'
            USING ERRCODE = '22004';
    END IF;
    RETURN price * rate;
END;
$$;

-- 또는 COALESCE로 기본값 처리
CREATE OR REPLACE FUNCTION get_discount_default(price NUMERIC, rate NUMERIC)
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
BEGIN
    RETURN COALESCE(price, 0) * COALESCE(rate, 0);
END;
$$;

원인 3 해결: 도메인 타입의 NOT NULL 제약 처리

-- 도메인 정의 확인
SELECT
    domain_name,
    data_type,
    domain_default,
    character_maximum_length
FROM information_schema.domains
WHERE domain_schema = 'public';

-- 예: NOT NULL 도메인 정의
CREATE DOMAIN positive_price AS NUMERIC
    NOT NULL
    CHECK (VALUE > 0);

-- 해당 도메인을 사용하는 테이블에 NULL 삽입 시 에러
CREATE TABLE products (
    product_id SERIAL PRIMARY KEY,
    price      positive_price
);

INSERT INTO products (product_id, price) VALUES (1, NULL);
-- ERROR: domain positive_price does not allow null values (SQLSTATE 22004)

-- 해결책 1: 도메인에서 NOT NULL 제거 (도메인 재생성 필요)
DROP DOMAIN positive_price CASCADE;
CREATE DOMAIN positive_price AS NUMERIC
    CHECK (VALUE > 0);  -- NOT NULL 제거

-- 해결책 2: 삽입 전 NULL 값을 기본값으로 치환
INSERT INTO products (product_id, price)
VALUES (1, COALESCE(NULL, 0.01)); -- 최소 가격으로 치환

-- 도메인을 사용하는 컬럼 목록 확인 쿼리
SELECT
    c.table_name,
    c.column_name,
    c.domain_name
FROM information_schema.columns c
WHERE c.domain_name = 'positive_price'
  AND c.table_schema = 'public';

예방 방법

1. 애플리케이션 레벨과 DB 레벨의 이중 유효성 검사 체계 구축

DB 제약 조건만 믿지 말고, 애플리케이션 레이어에서 먼저 NULL 여부를 검증하는 습관을 들여야 합니다. DB는 최후의 방어선으로 두되, 비즈니스 로직 상 NULL이 될 수 없는 필드는 API 입력 유효성 검사 단계에서 미리 차단하세요. 또한 신규 테이블 설계 시 DDL 리뷰 단계에서 NOT NULL 컬럼에 반드시 DEFAULT 값을 함께 지정하는 것을 팀 표준으로 정하는 것이 좋습니다.

-- 권장 테이블 설계 예시: NOT NULL 컬럼에는 DEFAULT 값 함께 지정
CREATE TABLE orders (
    order_id    SERIAL PRIMARY KEY,
    customer_id INTEGER      NOT NULL DEFAULT 0,
    status      VARCHAR(20)  NOT NULL DEFAULT 'pending',
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

2. 정기적인 데이터 품질 점검 쿼리 스케줄링

운영 중인 DB에서 주기적으로 NULL 데이터 현황을 점검하는 모니터링 쿼리를 배치 또는 pg_cron으로 스케줄링하면 문제를 사전에 감지할 수 있습니다. 특히 외부 시스템과 연동되거나 ETL 파이프라인이 연결된 테이블은 더욱 주의 깊게 모니터링해야 합니다.

-- NOT NULL 컬럼의 NULL 데이터 현황을 점검하는 동적 쿼리 생성
SELECT
    'SELECT COUNT(*) FROM ' || table_name ||
    ' WHERE ' || column_name || ' IS NULL;' AS check_query
FROM information_schema.columns
WHERE table_schema = 'public'
  AND is_nullable = 'NO'
  AND column_default IS NULL;

관련 에러

  • 23502 (not_null_violation): 22004와 가장 혼동되기 쉬운 에러로, INSERT/UPDATE 시 NOT NULL 제약 위반을 나타냅니다. 22004는 주로 함수나 도메인 수준에서 발생하고, 23502는 일반적인 테이블 컬럼 제약 위반에서 발생합니다.
  • 22023 (invalid_parameter_value): 함수나 연산자에 유효하지 않은 값이 전달될 때 발생하며, NULL 처리 로직과 함께 고려해야 하는 경우가 많습니다.
  • 22003 (numeric_value_out_of_range): 도메인 타입에 CHECK 제약과 NOT NULL이 함께 걸린 경우, 값의 범위 초과 시 연계하여 발생할 수 있는 에러입니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기