
DB에 익숙하지 않은나.... SQL문을 작성하는 도중에 에러를 만났다.
사실 엄청 쉬운 명령어인데 에러가 나오니 당황스러웠다. 그래서 에러를 기록하고 쿼리문을 이해하고자 아래에 자세히 작성을 해본다.
1. 사건 발생
멀쩡히 존재하는 테이블을 조회했는데 이런 에러가 났습니다.

select * from tb_warning_data_info limit 5;
SQL Error [42P01]: ERROR: relation "tb_warning_data_info" does not exist
Position: 15
DBeaver 왼쪽 트리에는 그 테이블이 분명히 보이는데도 "존재하지 않는다"고 한다...왜...도대체 왜!!!!!!!!
2. 에러 메시지 해부 : 정보가 3개 들어있다
에러 메시지를 통째로 읽고 넘기지 말고 조각내서 보자.
① 42P01 : SQLSTATE 코드
SQL 표준이 정한 5자리 에러 코드입니다. 42P01은 undefined_table 을 뜻한다.
| 코드 | 의미 | 상황 |
| 42P01 | undefined_table | 테이블/뷰를 못 찾음 |
| 42703 | undefined_column | 컬럼을 못 찾음 |
| 42601 | syntax_error | 문법 오류 |
| 23505 | unique_violation | 중복 키 |
| 23502 | not_null_violation | NULL 제약 위반 |
| 23503 | foreign_key_violation | 참조 무결성 위반 |
💡 검색 팁: 한글 에러 메시지보다 이 코드로 검색하는 게 훨씬 정확합니다.
📖 전체 목록: PostgreSQL 문서 → Appendix A. PostgreSQL Error Codes
② relation : 왜 "table"이 아닌가
PostgreSQL은 테이블 · 뷰 · 시퀀스 · 인덱스를 통틀어 relation이라 부릅니다.
관계형 모델(relational model)의 용어가 그대로 에러 메시지에 나오는 것입니다.
즉 이 에러는 "그 이름을 가진 어떤 객체도 못 찾았다"는 뜻입니다.
③ Position: 15 : 어디가 문제인가
select * from tb_warning_data_info limit 5;
123456789012345
↑ 15번째 글자 = 테이블명이 시작하는 위치
select * from 이 정확히 14글자이므로, 15번은 테이블명의 첫 글자입니다.
이게 중요한 이유는, 파싱은 성공했고 이름만 못 찾았다는 걸 알려주기 때문입니다. 문법 오타가 아니라 이름 또는 위치 문제라는 뜻이죠. 문법 오류였다면 42601이 났을 겁니다.
3. 원인 A : search_path (가장 흔함)
PostgreSQL은 스키마를 생략한 테이블 이름을 만나면 search_path에 등록된 스키마만 순서대로 뒤집니다. 여기 없으면 테이블이 실제로 존재해도 "does not exist"가 납니다.

현재 탐색 범위 확인

SHOW search_path;
실행 결과
search_path
------------------------------
"$user", public, topology
항목 의미
| 항목 | 의미 |
| "$user" | 접속 계정명과 같은 이름의 스키마 (보통 존재하지 않음) |
| public | 기본 스키마 |
| topology | PostGIS 확장이 생성한 스키마 → 이 DB에 공간정보 기능이 있다는 단서 |
문제의 테이블은 이 셋 중 어디에도 없었습니다. 그래서 못 찾은 것입니다.
비유: 책이 3층 서고에 있는데 사서가 1층만 뒤지고 "없습니다"라고 답한 상황입니다. 책이 없는 게 아니라, 찾는 범위 밖에 있었을 뿐입니다.
실제 위치 찾기

SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_name LIKE '%warning%';
LIKE '%warning%'로 부분 일치 검색을 하는 이유는, 이름을 잘못 기억했을 가능성까지 함께 잡기 위해서입니다.
- 실행 결과 : 8건 이상이 나왔고, table_schema 컬럼에 실제 스키마명이 있었습니다.
해결책 2가지
-- 방법 A: 정규화된 이름 사용 (권장)
SELECT * FROM 스키마명.tb_warning_data_info LIMIT 5;
-- 방법 B: search_path에 추가 (현재 세션에만 적용)
SET search_path TO 스키마명, public;
SELECT * FROM tb_warning_data_info LIMIT 5; -- 이제 스키마 없이도 동작
🔬 실험 1 : SET은 세션에만 산다
SHOW search_path; -- ① "$user", public, topology
SET search_path TO 스키마명, public;
SHOW search_path; -- ② 스키마명, public (변경됨)
이제 DBeaver 연결을 끊었다가 다시 붙이고 실행해보세요.
SHOW search_path; -- ③ "$user", public, topology (원복!)
SET은 현재 세션에만 사는 임시 설정입니다. 이 세 번의 확인이 "세션"이라는 개념을 체감하는 실험입니다.
영구 변경은 ALTER ROLE ... SET search_path 또는 postgresql.conf로 하지만, 초보 단계에서는 방법 A(스키마 명시)를 쓰세요. 다른 스키마에 동명 테이블이 있을 때 내가 뭘 조회 중인지 헷갈리지 않습니다.
🔬 실험 2 : public은 왜 생략해도 되나

SELECT * FROM public.tb_cd LIMIT 5; -- ①
SELECT * FROM tb_cd LIMIT 5; -- ②
결과가 완전히 같습니다. public은 search_path에 있으니 생략이 통하고, 다른 스키마는 목록에 없어서 생략이 안 되는 것입니다. 이 한 쌍의 비교가 search_path가 하는 일을 증명합니다.
4. 원인 B : 대소문자 folding
위 LIKE '%warning%' 조회가 0건이라면 이쪽이 원인입니다.
PostgreSQL은 따옴표 없는 식별자를 전부 소문자로 접습니다(fold).
이건 SQL 표준을 따르는 동작입니다. (참고: 표준은 대문자로 접도록 규정하지만, PostgreSQL은 소문자를 택했습니다)
🔬 실험 3 : 대문자 테이블 만들고 조회 실패시키기
BEGIN;
CREATE TABLE "TB_Test" (id int); -- 큰따옴표로 대문자 고정
SELECT * FROM TB_Test; -- ①
SELECT * FROM "TB_Test"; -- ②
ROLLBACK;
① 실행 결과 — 실패
ERROR: relation "tb_test" does not exist
여기가 결정적 단서입니다. 에러 메시지에 찍힌 이름이 내가 입력한 TB_Test가 아니라 소문자 tb_test 입니다. PostgreSQL이 내 입력을 소문자로 접은 뒤 찾았다는 증거죠.
② 실행 결과 : 성공. 큰따옴표로 감싸면 입력한 그대로 유지됩니다.
실무 규칙
상황 결과
| 상황 | 결과 |
| CREATE TABLE MyTable | 실제로는 mytable로 생성됨 |
| CREATE TABLE "MyTable" | MyTable로 생성됨 → 조회 시 항상 큰따옴표 필요 |
| SELECT * FROM MYTABLE | mytable을 찾음 (대소문자 무관하게 소문자로 접힘) |
💡 따옴표 없이 쓰면 대소문자를 아무렇게나 써도 되지만, 한 번 큰따옴표로 대문자를 박아넣으면 평생 큰따옴표를 달고 다녀야 합니다. 테이블명은 처음부터 소문자+언더스코어로 만드는 게 정석인 이유입니다.
5. 원인 C : 다른 데이터베이스에 접속
트리에는 보이는데 조회가 안 된다면, 다른 DB에 붙어 있을 가능성입니다.
SELECT current_database();
PostgreSQL에서는 하나의 연결이 하나의 데이터베이스에만 붙습니다. 다른 DB의 테이블은 같은 세션에서 조회할 수 없습니다 (MySQL과 다른 점).
6. 진단 순서

7. 정리
| 확인항목 | 쿼리 |
| 에러 코드 의미 | 42P01 = undefined_table |
| 테이블 실제 위치 | SELECT table_schema, table_name FROM information_schema.tables WHERE table_name LIKE '%키워드%'; |
| 현재 탐색 범위 | SHOW search_path; |
| 현재 데이터베이스 | SELECT current_database(); |
| 해결 (권장) | SELECT * FROM 스키마명.테이블명; |
| 해결 (임시) | SET search_path TO 스키마명, public; |
| 대소문자 문제 | SELECT * FROM "TableName"; |
- 핵심 교훈: does not exist는 "없다"가 아니라 "내가 찾은 범위에 없다" 는 뜻입니다.
'개발 및 기타 등등 > Database' 카테고리의 다른 글
| DataBase Query문(쿼리문) 이해하기, SELECT와 WHERE (1) | 2026.09.05 |
|---|---|
| DataBase 기본(Postg데이터베이스 구조와 메타데이터 읽기 (0) | 2026.09.03 |
| PostgreSQL 기본 개념과 SQL에 대한 기본지식 파헤치기! (0) | 2026.09.02 |
| DataBase(DB) PostgreSQL + DBeaver 실습하기! [Query(쿼리)문 작성해보기 WHERE로 데이터 더 많이 추가해보기, DB처리순서, NULL제약조건] (1) | 2026.07.19 |
| DataBase(DB) PostgreSQL + DBeaver 실습하기! [Query(쿼리)문 작성해보기 INSERT, WHERE, SELECT] (0) | 2026.07.18 |