# api 오답노트

<!-- 형식:
### YYYY-MM-DD — 실패 요약 제목
**증상**: 어떤 오동작이 나타났는지
**원인**: 왜 그랬는지
**해결**: 어떻게 고쳤는지
**교훈**: 다음에 조심할 것 (한 줄)
-->

### 2026-04-22 — pg placeholder 상대 offset 계산 실수로 `$1` 재사용 (42P18)
**증상**: 영업관리 계약을 "수주(0000964)"로 저장하면 `inconsistent types deduced for parameter $1` 오류.
**원인**: `src/app/api/sales/contract/save/route.ts`에서 동적으로 조립한 projectNoSql이 `$${projectNoParams.length + 1}` (push 전 length=0 → `$1`)이라 이미 `projectObjId` INSERT(bigint)용으로 쓰이던 `$1`을 `COALESCE($1, '') || '-' || ...` (text)에서 재사용 → PG 타입 추론 실패. 뒤 이어지는 `$${length + 2/3/4/5}`도 고정 `$1..$4`와 연쇄 충돌.
**해결**: 최종 파라미터 배열 순서대로 절대 번호 `$1..$9`를 하드코딩. projectNoSql은 `$5`로 고정(그 앞에 `$1..$4` 고정 사용).
**교훈**: 동적 SQL 파라미터 번호는 "최종 배열의 절대 인덱스" 기준으로 매길 것. 서브 배열 길이 기반 offset은 상위 고정 파라미터 개수를 빠뜨리기 쉬움. 같은 `$N`이 text/numeric 두 컨텍스트에 있으면 PG가 뱉는다.

### 2026-04-22 — 같은 $N을 두 테이블 컬럼(INSERT+WHERE)에 재사용하면 42P18
**증상**: `$1` 충돌 수정 후에도 `inconsistent types deduced for parameter $2` 계속 발생.
**원인**: `SELECT $1, $2, ... FROM contract_mgmt WHERE objid = $2`에서 `$2`가 (a) `project_mgmt.contract_objid` INSERT + (b) `contract_mgmt.objid` WHERE 비교로 두 번 사용. 두 컬럼이 둘 다 varchar라도 길이 제약(`varchar(20)` vs `varchar(50)`)이나 varchar/char 혼재, 또는 pg driver의 위치별 타입 추론 차이 등 미묘한 이유로 단일 파라미터가 양쪽을 모두 만족시키지 못해 터질 수 있음. ("bigint vs numeric" 같은 단순 수치 타입 차이만 원인이 아님 — 실제로 이 건은 둘 다 varchar였음)
**해결**: 동일 값을 배열 끝에 한 번 더 push해서 WHERE절용 `$10`으로 분리.
**교훈**: 같은 파라미터를 서로 다른 테이블 컬럼을 참조하는 두 위치(INSERT 대상 + WHERE 비교)에서 재사용하지 말 것. 겉보기에 같은 타입처럼 보여도 42P18 터질 수 있음. 분리하거나 명시 캐스팅(`$N::text`, `$N::bigint`)을 쓰는 게 안전.

