빌드노트
PostgreSQL에 "OrderItem"이 있는데 OrderItem 조회는 실패한다
PostgreSQL에서 "OrderItem"과 orderitem은 서로 다른 테이블일 수 있다. 실제 DB·스키마·철자를 조회하고, 세 가지 이름 표기의 반환값과 SQLSTATE를 비교해 일관된 참조 방식을 고른다.
PostgreSQL 관리 도구에는 OrderItem 테이블이 보이는데 SELECT ... FROM OrderItem은 relation "orderitem" does not exist로 실패할 수 있다. 테이블을 큰따옴표로 감싼 "OrderItem"이라는 이름으로 만들었다면, 따옴표 없는 OrderItem은 다른 이름인 orderitem으로 해석된다. 현재 접속 DB와 실제 스키마를 확인한 뒤, 저장된 철자에 맞춰 스키마."OrderItem"으로 조회한다.
PostgreSQL은 따옴표 없는 식별자를 소문자로 접고, 큰따옴표로 감싼 식별자는 대소문자를 구분한다. 따라서 OrderItem, ORDERITEM, orderitem은 같은 이름을 가리키지만 "OrderItem"은 그 셋과 다르다. 여기서 식별자는 테이블·열 같은 객체의 이름이다. 행에 저장된 문자열의 대소문자 비교와는 별개의 규칙이다. PostgreSQL 18 식별자 규칙 (새 탭에서 열림)
오류가 난 연결에서 이름을 확인한다
관리 도구와 애플리케이션이 같은 서버·포트·DB를 보는지 먼저 대조한다. 다음 두 문장은 실패한 SQL과 같은 연결에서 현재 DB·실행 사용자·기본 스키마와 검색 경로를 읽는다. 연결 풀을 쓴다면 다른 연결을 새로 열어 확인한 값과 혼동하지 않는다.
SELECT current_database(), current_user, current_schema();
SHOW search_path;
이번 별도 실험에서는 DB가 identifier_lab, 사용자가 lab_owner, 기본 스키마가 public이었고 검색 경로는 "$user", public이었다. current_schema()는 검색 경로에서 기본으로 사용할 스키마를 알려 준다. 문제의 테이블이 그곳에 저장돼 있다는 뜻은 아니다. 접속 정보 함수 (새 탭에서 열림)
다음 조회는 현재 DB에서 OrderItem과 대소문자만 다른 일반·파티션 테이블의 실제 스키마와 철자를 찾는다. 자신의 테이블 이름을 찾을 때는 조건의 'orderitem'을 그 이름의 소문자 표기로 바꾼다.
SELECT n.nspname AS schema_name, c.relname AS table_name
FROM pg_catalog.pg_class AS c
JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
WHERE lower(c.relname) = 'orderitem'
AND c.relkind IN ('r', 'p')
ORDER BY n.nspname, c.relname;
소문자 테이블까지 만든 뒤 실행한 결과에는 quote_lab | OrderItem과 quote_lab | orderitem 두 행이 있었다. 여기서 lower()는 후보를 모으기 위한 조건일 뿐, 원래 이름을 바꾸지 않는다. 출력된 table_name의 철자를 그대로 읽는다. 이 조회는 뷰·외부 테이블을 제외하며 다른 DB의 테이블도 찾지 않는다. 결과가 없으면 관리 도구의 접속 대상과 테이블을 만든 DDL부터 다시 대조한다. pg_class의 이름·종류 (새 탭에서 열림), pg_namespace의 스키마 이름 (새 탭에서 열림)
대상이 quote_lab의 OrderItem이라면 실제 조회문에서 테이블 참조를 quote_lab."OrderItem"으로 맞춘다. 스키마 이름도 혼합 대소문자로 만들었다면 그 부분을 따로 큰따옴표로 감싼다. "quote_lab.OrderItem"처럼 점까지 한 쌍으로 묶으면 스키마와 테이블로 나뉘지 않는다. 작은따옴표는 문자열을 표시하므로 테이블 이름의 큰따옴표 대신 쓸 수 없다. 이름과 문자열의 문법 (새 탭에서 열림)
따옴표를 맞춰도 스키마를 생략하면 검색 경로 밖의 테이블은 찾지 못할 수 있다. 별도 실험에서 검색 경로는 그대로 둔 채 "OrderItem"과 public."OrderItem"을 참조하면 둘 다 42P01이었고, quote_lab."OrderItem"으로 지정하면 mixed 행을 읽었다. 스키마 검색 경로 (새 탭에서 열림)
접속 DB도 별도로 확인해야 한다. 새 other_lab DB에는 quote_lab 스키마만 만들고 테이블을 만들지 않았다. 그 연결에서는 정확한 quote_lab."OrderItem"도 42P01이었다. schema.table은 현재 DB 안의 이름이므로, 스키마를 붙여도 다른 DB의 테이블로 넘어가지 않는다. 관리 도구의 테이블 목록과 오류가 난 연결의 DB를 먼저 맞춰야 하는 이유다. DB와 스키마의 범위 (새 탭에서 열림)
오류가 사라져도 같은 테이블을 읽는 것은 아니다
Windows의 Docker Desktop에서 새 PostgreSQL 18.6 서버와 전용 DB를 만들어 세 표기를 비교했다. 서버·클라이언트 모두 18.6이었고, 소유 스키마의 합성 행만 사용했다. 검색 경로는 바꾸지 않았다.
직접 비교하려면 스키마 생성 권한이 있는 실습 계정으로 빈 전용 DB에 접속한다. quote_lab이 이미 있는 DB에서는 실행하지 않는다. 자동 커밋을 켜고 BEGIN 없이 문장별로 실행하며, 생성이 실패하면 비교를 중단한다. 다음 입력은 "OrderItem"에 mixed 한 행을 만든 뒤 세 이름을 조회한다. CREATE TABLE AS는 조회 결과로 테이블과 초기 행을 함께 만든다. CREATE TABLE AS (새 탭에서 열림)
CREATE SCHEMA quote_lab;
CREATE TABLE quote_lab."OrderItem" AS SELECT 'mixed'::text AS marker;
SELECT marker FROM quote_lab.OrderItem;
SELECT marker FROM quote_lab."OrderItem";
SELECT marker FROM quote_lab."orderitem";
첫 SELECT와 마지막 SELECT의 오류는 예상된 비교 결과다. 오류 뒤 다음 문장을 따로 실행한다. psql에서는 각 SELECT 직후 \echo :SQLSTATE로 코드를 읽을 수 있다. SQLSTATE는 직전 SQL의 결과이며 성공은 00000이다. psql의 SQLSTATE (새 탭에서 열림)
이어서 같은 실습 DB에 소문자 테이블을 추가하고 위 SELECT 세 문장만 다시 실행한다.
CREATE TABLE quote_lab.orderitem AS SELECT 'lowercase'::text AS marker;
실제 결과를 소문자 테이블을 만들기 전 → 만든 뒤 순서로 정리하면 다음과 같다.
quote_lab.OrderItem:42P01오류 →lowercase한 행,00000.quote_lab."OrderItem":mixed한 행,00000→ 그대로mixed한 행,00000.quote_lab."orderitem":42P01오류 →lowercase한 행,00000.
처음 오류 메시지는 relation "quote_lab.orderitem" does not exist였다. 입력의 대문자가 소문자로 해석됐다는 단서다. 42P01의 공식 조건명은 undefined_table이며, 이 코드만으로 따옴표가 원인이라고 확정할 수는 없다. PostgreSQL 오류 코드 (새 탭에서 열림)
소문자 테이블이 생기면 세 조회가 모두 성공하지만 대상은 둘로 갈린다. 따옴표 없는 quote_lab.orderitem도 별도로 실행해 lowercase를 확인했다. 오류가 사라졌다는 사실만으로 의도한 테이블을 읽었다고 판단하면 안 된다.
생성할 때와 참조할 때 이름 규칙을 맞춘다
기존 이름이 "OrderItem"이라면 조회·매핑·마이그레이션에서 같은 철자와 큰따옴표를 유지하는 쪽으로 맞춘다. ORM을 쓴다면 최종 생성 SQL의 테이블 참조도 확인한다. 새 테이블의 이름 규약을 정할 수 있다면 order_item처럼 따옴표 없이 쓸 수 있는 소문자 이름으로 생성과 참조를 통일할 수 있다. CREATE TABLE OrderItem은 대문자를 보존하는 선언이 아니라 소문자 orderitem을 만드는 선언이다.
이 실험은 합성 테이블의 이름 해석과 DB·스키마 범위를 확인했다. 운영 테이블 이름 변경, 검색 경로 변경, ORM별 자동 인용 설정, 계정별 접근 권한 차이는 검증하지 않았다. 이미 쓰이는 혼합 대소문자 이름을 소문자로 바꾸는 이관은 참조 코드와 충돌할 이름을 조사해야 하는 별도 작업이다.