2026년 08월 17일 | DBMS Error 가이드
이 글에서 다루는 내용
22011 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22011 substring error 는?
PostgreSQL 에러 코드 22011은 substring error로, 문자열에서 특정 부분을 추출하는 SUBSTRING() 함수나 관련 문자열 처리 함수를 사용할 때 잘못된 인자값이 전달되었을 때 발생합니다. 주로 시작 위치(start position)나 길이(length) 인자로 음수 값이 전달되거나, 유효하지 않은 범위가 지정되었을 때 트리거됩니다. 실무에서는 사용자 입력값을 그대로 쿼리에 사용하거나, 동적 SQL을 생성할 때 유효성 검사를 빠뜨린 경우에 자주 나타납니다.
주요 발생 원인
- SUBSTRING 함수에 음수 길이(length) 값 전달
SUBSTRING(string FROM start FOR length) 구문에서 length 인자로 음수 값을 전달하면 PostgreSQL은 즉시 22011 에러를 발생시킵니다. 이는 SQL 표준에서 길이 값은 반드시 0 이상의 정수여야 한다고 명시하고 있기 때문입니다. 사용자 입력값이나 계산된 값을 검증 없이 그대로 length 인자로 넘길 때 가장 흔하게 발생합니다.
- 동적 계산된 시작 위치 또는 길이 값의 유효성 부재
애플리케이션에서 문자열 파싱 로직을 구현할 때, 두 위치 값의 차이를 length로 계산하는 경우가 많습니다. 예를 들어 end_pos - start_pos 같은 계산 결과가 음수가 되는 상황이 발생하면 에러가 트리거됩니다. 특히 구분자(delimiter)를 기준으로 문자열을 분할할 때 구분자가 없거나 예상치 못한 위치에 있으면 이런 문제가 생깁니다.
- OVERLAY 또는 정규식 기반 SUBSTRING 함수의 잘못된 인자 사용
PostgreSQL은 SUBSTRING(string FROM pattern FOR escape) 형태의 SQL 정규식 기반 함수와 OVERLAY() 함수도 지원하는데, 이 함수들에 잘못된 형식의 패턴이나 범위 값이 전달될 때도 22011 에러가 발생할 수 있습니다. OVERLAY() 함수의 PLACING 절에서 시작 위치와 교체 길이가 잘못 지정되면 동일한 에러 코드로 처리됩니다.
해결 방법
원인 1 해결: 음수 길이 값 방지
음수가 될 가능성이 있는 length 값은 GREATEST() 함수를 사용하여 최솟값을 0으로 고정하거나, CASE 구문으로 조건 처리하세요.
-- 문제가 되는 쿼리 예시
SELECT SUBSTRING('Hello World' FROM 5 FOR -3);
-- ERROR: negative substring length not allowed
-- 해결 방법 1: GREATEST() 함수로 최솟값 보장
SELECT SUBSTRING('Hello World' FROM 5 FOR GREATEST(0, -3));
-- 결과: '' (빈 문자열 반환, 에러 없음)
-- 해결 방법 2: CASE 구문으로 유효성 검사
SELECT
CASE
WHEN length_val < 0 THEN ''
ELSE SUBSTRING(target_string FROM start_pos FOR length_val)
END AS safe_substring
FROM (
SELECT 'Hello World' AS target_string, 5 AS start_pos, -3 AS length_val
) sub;
-- 실무 예시: 사용자 입력값을 받는 경우
CREATE OR REPLACE FUNCTION safe_substring(
p_string TEXT,
p_start INTEGER,
p_length INTEGER
) RETURNS TEXT AS $$
BEGIN
IF p_length < 0 THEN
RAISE WARNING '음수 길이 값이 전달되었습니다. 빈 문자열을 반환합니다.';
RETURN '';
END IF;
IF p_start < 1 THEN
p_start := 1;
END IF;
RETURN SUBSTRING(p_string FROM p_start FOR p_length);
END;
$$ LANGUAGE plpgsql;
-- 함수 사용 예시
SELECT safe_substring('PostgreSQL DBA', 1, -5); -- '' 반환
SELECT safe_substring('PostgreSQL DBA', 1, 10); -- 'PostgreSQL' 반환
원인 2 해결: 동적 계산 길이의 유효성 검증
-- 문제 상황: 구분자 기반 파싱에서 발생하는 음수 길이
-- 예: 'user@domain.com' 에서 도메인 추출 시도
WITH email_data AS (
SELECT 'invalid-email' AS email -- '@' 없는 잘못된 입력
)
SELECT
CASE
WHEN POSITION('@' IN email) = 0 THEN '유효하지 않은 이메일'
ELSE SUBSTRING(
email
FROM POSITION('@' IN email) + 1
FOR LENGTH(email) - POSITION('@' IN email)
)
END AS domain_part
FROM email_data;
-- 더 안전한 방법: SPLIT_PART() 함수 활용
SELECT SPLIT_PART('user@domain.com', '@', 2) AS domain_part;
-- 결과: 'domain.com'
SELECT SPLIT_PART('invalid-email', '@', 2) AS domain_part;
-- 결과: '' (에러 없이 빈 문자열 반환)
-- 동적 계산 시 NULL 체크 및 범위 검증 예시
WITH string_positions AS (
SELECT
'START_DATA_END' AS raw_string,
POSITION('START_' IN 'START_DATA_END') + LENGTH('START_') AS start_pos,
POSITION('_END' IN 'START_DATA_END') AS end_pos
)
SELECT
CASE
WHEN end_pos <= start_pos THEN NULL
ELSE SUBSTRING(raw_string FROM start_pos FOR end_pos - start_pos)
END AS extracted_value
FROM string_positions;
-- 결과: 'DATA'
원인 3 해결: OVERLAY 함수 올바른 사용
-- 문제가 되는 OVERLAY 사용
-- OVERLAY(string PLACING new_string FROM start FOR length)
SELECT OVERLAY('Hello World' PLACING 'PostgreSQL' FROM 1 FOR -5);
-- ERROR 발생
-- 올바른 OVERLAY 사용
SELECT OVERLAY('Hello World' PLACING 'PostgreSQL' FROM 1 FOR 5);
-- 결과: 'PostgreSQL World'
-- 안전한 OVERLAY 래퍼 함수
CREATE OR REPLACE FUNCTION safe_overlay(
p_original TEXT,
p_replacement TEXT,
p_start INTEGER,
p_length INTEGER DEFAULT NULL
) RETURNS TEXT AS $$
DECLARE
v_length INTEGER;
BEGIN
v_length := COALESCE(p_length, LENGTH(p_replacement));
IF p_start < 1 OR v_length < 0 THEN
RAISE EXCEPTION '유효하지 않은 OVERLAY 인자: start=%, length=%', p_start, v_length;
END IF;
RETURN OVERLAY(p_original PLACING p_replacement FROM p_start FOR v_length);
END;
$$ LANGUAGE plpgsql;
예방 방법
- 입력값 유효성 검사 레이어 구축
애플리케이션 레이어와 데이터베이스 레이어 모두에서 SUBSTRING() 함수에 전달되는 인자를 검증하는 습관을 들이세요. PostgreSQL의 CHECK 제약조건이나 트리거를 활용하여 데이터 입력 단계에서부터 유효하지 않은 값을 차단할 수 있습니다. 또한, 모든 문자열 처리 로직을 래퍼 함수로 캡슐화하여 중앙에서 유효성을 관리하면 코드 재사용성과 안전성을 동시에 확보할 수 있습니다.
- GREATEST(), NULLIF(), COALESCE() 조합으로 방어적 쿼리 작성
문자열 처리 쿼리를 작성할 때는 항상 GREATEST(0, calculated_length) 패턴을 사용하여 길이 값이 절대 음수가 되지 않도록 보장하세요. NULLIF()로 0이나 의미 없는 값을 NULL로 변환하고, COALESCE()로 기본값을 설정하는 방어적 프로그래밍 패턴을 SQL 레벨에서도 적극 적용하는 것이 좋습니다.
-- 방어적 쿼리 패턴 예시
SELECT
COALESCE(
NULLIF(
SUBSTRING(
column_name
FROM GREATEST(1, start_position)
FOR GREATEST(0, end_position - start_position)
),
''
),
'DEFAULT_VALUE'
) AS safe_result
FROM your_table;
관련 에러
- 22001 (string_data_right_truncation): 문자열 데이터가 대상 컬럼의 최대 길이를 초과할 때 발생하며, 문자열 처리 과정에서 22011과 함께 나타나는 경우가 있습니다.
- 22007 (invalid_datetime_format): 날짜/시간 형식의 문자열을 파싱할 때
SUBSTRING으로 잘못된 부분을 추출 후 캐스팅 시 연쇄적으로 발생할 수 있습니다. - 22003 (numeric_value_out_of_range): 문자열에서 추출한 숫자 값이 대상 타입의 범위를 벗어날 때 발생하며, SUBSTRING 결과를 숫자로 변환할 때 22011 이후에 나타날 수 있습니다.
- 42883 (undefined_function): SUBSTRING 함수에 잘못된 타입의 인자를 전달할 때 PostgreSQL이 적합한 함수 오버로드를 찾지 못할 경우 발생합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.