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

42602
2026년 09월 09일 | DBMS Error 가이드

이 글에서 다루는 내용

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

42602 invalid name 는?

PostgreSQL 에러 코드 42602 (invalid_name) 은 데이터베이스 객체(테이블, 컬럼, 인덱스, 함수 등)의 이름이 PostgreSQL의 명명 규칙을 위반했을 때 발생하는 에러입니다. 식별자(identifier)는 문자, 숫자, 언더스코어로만 구성되어야 하며, 특수문자나 예약어를 무분별하게 사용하면 이 에러가 트리거됩니다. 주로 동적 SQL을 생성하거나, 외부 시스템에서 가져온 이름을 그대로 사용할 때, 혹은 마이그레이션 작업 중 비표준 식별자를 처리할 때 자주 마주치는 에러입니다.


주요 발생 원인

1. 식별자에 허용되지 않는 특수문자 사용

PostgreSQL 식별자는 기본적으로 영문자(a-z), 숫자(0-9), 언더스코어(_)만 허용하며, 반드시 문자 또는 언더스코어로 시작해야 합니다. 하이픈(-), 점(.), 공백, 달러 기호($) 등의 특수문자가 포함된 이름을 따옴표 없이 사용하면 파서가 이를 올바른 식별자로 인식하지 못해 42602 에러를 발생시킵니다. 특히 외부 API나 레거시 시스템에서 넘어온 컬럼명을 그대로 DDL에 삽입하는 경우 이 상황이 빈번하게 발생합니다.

2. 동적 SQL 생성 시 식별자 처리 미흡

PL/pgSQL 또는 애플리케이션 레이어에서 문자열 연결(concatenation)을 통해 SQL을 동적으로 생성할 때, 사용자 입력값이나 변수를 적절한 인용 처리 없이 식별자 위치에 삽입하면 에러가 발생합니다. quote_ident() 함수나 format() 함수의 %I 포맷 지시자를 사용하지 않으면 공백이나 특수문자가 포함된 이름이 파서를 혼란스럽게 만들 수 있습니다. 이는 보안 취약점(SQL Injection)으로도 이어질 수 있는 매우 중요한 문제입니다.

3. 숫자로 시작하는 식별자 사용

SQL 표준 및 PostgreSQL 명명 규칙에 따르면, 비따옴표(unquoted) 식별자는 반드시 문자 또는 언더스코어로 시작해야 합니다. 1st_column, 2024_report 처럼 숫자로 시작하는 이름을 큰따옴표 없이 사용하면 PostgreSQL 파서가 이를 숫자 리터럴의 일부로 해석하려다 실패하고, 결국 42602 에러를 반환합니다. 이 경우 큰따옴표로 감싸면 파싱은 성공하지만, 이후 매번 따옴표를 붙여야 하는 유지보수 부담이 생기므로 처음부터 올바른 명명 규칙을 따르는 것이 좋습니다.


해결 방법

원인 1: 특수문자 포함 식별자 처리

특수문자가 포함된 이름은 큰따옴표(")로 감싸서 사용할 수 있습니다. 단, 이 방법은 대소문자를 구분하게 되며, 이후 모든 참조에서 동일하게 따옴표를 사용해야 합니다.

-- 에러 발생: 하이픈이 포함된 테이블명
CREATE TABLE user-data (
    id SERIAL PRIMARY KEY
);
-- ERROR:  42602: invalid name

-- 해결 방법 1: 큰따옴표로 감싸기
CREATE TABLE "user-data" (
    id SERIAL PRIMARY KEY,
    name TEXT
);

-- 이후 참조 시에도 따옴표 필수
SELECT * FROM "user-data";

-- 해결 방법 2 (권장): 명명 규칙을 준수한 이름으로 변경
CREATE TABLE user_data (
    id SERIAL PRIMARY KEY,
    name TEXT
);

-- 기존에 특수문자가 포함된 컬럼을 가진 테이블 마이그레이션
ALTER TABLE legacy_table RENAME COLUMN "first-name" TO first_name;
ALTER TABLE legacy_table RENAME COLUMN "last.name"  TO last_name;

원인 2: 동적 SQL에서 안전한 식별자 처리

PL/pgSQL에서 동적 SQL을 생성할 때는 반드시 quote_ident() 또는 format()의 %I를 사용해야 합니다.

-- 잘못된 방법: 문자열 연결로 식별자 삽입 (에러 및 보안 취약)
DO $$
DECLARE
    tbl_name TEXT := 'my-table'; -- 하이픈 포함
    sql_stmt TEXT;
BEGIN
    sql_stmt := 'SELECT * FROM ' || tbl_name; -- 위험!
    EXECUTE sql_stmt;
END;
$$;

-- 올바른 방법 1: quote_ident() 사용
DO $$
DECLARE
    tbl_name TEXT := 'my-table';
    sql_stmt TEXT;
BEGIN
    sql_stmt := 'SELECT * FROM ' || quote_ident(tbl_name);
    -- 결과: SELECT * FROM "my-table"
    EXECUTE sql_stmt;
END;
$$;

-- 올바른 방법 2 (권장): format() + %I 사용
DO $$
DECLARE
    tbl_name  TEXT := 'user data'; -- 공백 포함
    col_name  TEXT := 'first-name'; -- 하이픈 포함
    sql_stmt  TEXT;
BEGIN
    sql_stmt := format('SELECT %I FROM %I WHERE id = $1', col_name, tbl_name);
    -- 결과: SELECT "first-name" FROM "user data" WHERE id = $1
    EXECUTE sql_stmt USING 1;
END;
$$;

-- 실전 예제: 동적 테이블 생성 함수
CREATE OR REPLACE FUNCTION create_partition_table(schema_name TEXT, table_name TEXT)
RETURNS VOID AS $$
DECLARE
    full_table_name TEXT;
BEGIN
    -- format + %I로 안전하게 식별자 구성
    full_table_name := format('%I.%I', schema_name, table_name);
    EXECUTE format(
        'CREATE TABLE IF NOT EXISTS %s (
            id      BIGSERIAL PRIMARY KEY,
            created_at TIMESTAMPTZ DEFAULT NOW()
        )', full_table_name
    );
    RAISE NOTICE 'Table % created successfully.', full_table_name;
END;
$$ LANGUAGE plpgsql;

-- 사용 예
SELECT create_partition_table('public', 'orders_2024');

원인 3: 숫자로 시작하는 식별자 수정

-- 에러 발생: 숫자로 시작하는 컬럼명
CREATE TABLE report (
    2024_revenue NUMERIC(15,2)
);
-- ERROR:  42602: invalid name

-- 해결 방법 1: 큰따옴표 사용 (임시방편)
CREATE TABLE report (
    "2024_revenue" NUMERIC(15,2)
);

-- 해결 방법 2 (강력 권장): 접두어 추가로 규칙 준수
CREATE TABLE report (
    revenue_2024 NUMERIC(15,2),  -- 연도를 뒤로
    yr2024_revenue NUMERIC(15,2) -- 접두어 추가
);

-- 이미 생성된 컬럼 이름 변경
ALTER TABLE report RENAME COLUMN "2024_revenue" TO revenue_2024;

-- 이름 유효성 검사 쿼리 (사전 점검용)
SELECT
    table_name,
    column_name,
    CASE
        WHEN column_name ~ '^[0-9]'          THEN '숫자로 시작'
        WHEN column_name ~ '[^a-zA-Z0-9_]'   THEN '특수문자 포함'
        ELSE '정상'
    END AS name_issue
FROM information_schema.columns
WHERE table_schema = 'public'
  AND (
      column_name ~ '^[0-9]'
      OR column_name ~ '[^a-zA-Z0-9_]'
  );

예방 방법

1. 식별자 명명 규칙 표준화 및 린팅 도입

팀 내 DDL 작성 가이드라인을 수립하고, 모든 식별자는 소문자 영문자, 숫자, 언더스코어만 사용하며 반드시 문자로 시작하도록 규칙을 강제하세요. CI/CD 파이프라인에 sqlfluff, pgTAP 등의 SQL 린팅 도구를 통합하면 비표준 식별자가 포함된 마이그레이션 스크립트가 프로덕션에 배포되기 전에 자동으로 탐지할 수 있습니다. 아래 쿼리를 주기적으로 실행하여 데이터베이스 내 비표준 식별자를 모니터링하는 것도 좋은 습관입니다.

-- 비표준 식별자 전체 점검 쿼리
SELECT
    'table'     AS object_type,
    table_schema AS schema_name,
    table_name  AS object_name
FROM information_schema.tables
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
  AND table_name ~ '[^a-z0-9_]'  -- 소문자, 숫자, 언더스코어 외 문자 포함

UNION ALL

SELECT
    'column'    AS object_type,
    table_schema AS schema_name,
    column_name AS object_name
FROM information_schema.columns
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
  AND column_name ~ '[^a-z0-9_]'

ORDER BY object_type, schema_name, object_name;

2. 동적 SQL 코드 리뷰 체크리스트 적용

동적 SQL이 포함된 모든 PL/pgSQL 함수나 애플리케이션 코드에 대한 코드 리뷰 시, 식별자 위치에 quote_ident() 또는 format()의 %I가 반드시 사용되었는지 확인하는 항목을 체크리스트에 포함시키세요. 값(value) 위치에는 $1, $2 파라미터 바인딩 또는 quote_literal()을 사용하는 것이 원칙이며, 이를 혼동하지 않도록 팀 내 교육을 주기적으로 진행하는 것이 중요합니다.


관련 에러

  • 42601 (syntax_error): 식별자 문제가 구문 오류로 이어질 때 함께 발생하는 경우가 많습니다. 42602와 유사하게 파서 단계에서 감지됩니다.
  • 42P01 (undefined_table): 따옴표 처리 불일치로 인해 대소문자가 맞지 않는 테이블명을 참조하면 발생합니다. 예를 들어 "MyTable"로 생성 후 mytable로 접근하면 이 에러가 납니다.
  • 42703 (undefined_column): 동일한 이유(따옴표 처리 불일치)로 컬럼을 찾지 못할 때 발생하며, 42602 해결 후 연달아 마주칠 수 있는 에러입니다.
  • 42501 (insufficient_privilege): 동적 SQL로 객체를 생성하거나 접근할 때 권한 문제가 겹쳐 발생하는 경우가 있으므로, 동적 SQL 디버깅 시 함께 확인하세요.
DBMS 에러 코드 시리즈

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

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

댓글 남기기