테이블·타입·제약으로 데이터 설계하기
프로젝트 이름을 모든 작업 행에 복사하면 이름을 바꿀 때 여러 행을 함께 고쳐야 한다.
공통 예제가 projects와 tasks를 나누는 이유다.
프로젝트 이름은 프로젝트에 한 번 저장하고 작업은 그 프로젝트의 ID를 참조한다.
이 장에서 처음 나오는 말3개
기본 키Primary key- 각 행을 중복과 NULL 없이 식별하는 컬럼 또는 컬럼 묶음.
외래 키Foreign key- 다른 행을 참조할 때 그 대상이 존재하는지 강제하는 제약.
NULL- 값이 없거나 아직 모름을 나타낸다. 빈 문자열·0·false와 다르다.
관계를 저장하는 방법
섹션 제목: “관계를 저장하는 방법”projects.id = 1인 프로젝트에 작업 두 개가 속한다. 각 작업의 project_id가 1이므로
이것은 프로젝트 하나 대 작업 여러 개의 관계다. 프로젝트 이름을 바꿔도 작업 행의 ID 참조는 유지된다.
한 작업에 담당자 여러 명이 필요해지면 쉼표 문자열에 넣기보다 별도 연결 테이블을 고려한다.
| 규칙 | 공통 예제의 표현 | 막는 오류 |
|---|---|---|
| 행을 식별한다 | PRIMARY KEY | 같은 ID의 중복 |
| 이름이 필요하다 | NOT NULL | 값 없는 프로젝트 이름 |
| 프로젝트 이름은 중복되지 않는다 | UNIQUE | 이 예제의 이름 중복 |
| 제목은 빈 문자열이 아니다 | CHECK (char_length(title) > 0) | 빈 제목 |
| 프로젝트가 존재해야 한다 | REFERENCES public.projects(id) | 없는 프로젝트를 가리키는 작업 |
프로젝트 이름의 유일성은 이 예제의 업무 규칙이다. 모든 앱에서 이름을 유일하게 할 필요는 없다.
CHECK는 결과가 NULL이면 통과할 수 있으므로 필수 값에는 NOT NULL도 필요하다.
외래 키를 만들면 참조 대상의 유효성을 검사하지만, 참조하는 쪽의 tasks.project_id 인덱스까지
자동 생성하지는 않는다. (제약 조건)
타입은 데이터의 의미로 고른다
섹션 제목: “타입은 데이터의 의미로 고른다”| 저장할 값 | 출발점 | 판단할 점 |
|---|---|---|
| 이름·제목 | text | 길이 제한이 업무 규칙이면 별도 제약 |
| 내부 식별자 | bigint identity | 자동 번호에 빈틈이 생겨도 정상 |
| 여러 곳에서 생성할 ID | uuid | ID가 추측하기 어렵더라도 접근 권한 검사는 필요 |
| 사건이 발생한 시점 | timestamptz | 원래 입력한 지역 시간대 이름은 따로 보관해야 함 |
| 생일·정산일 | date | 특정 순간이 아니라 달력상의 날짜 |
| 정확한 소수 금액 | numeric | 통화·자릿수·반올림 규칙도 결정 |
| 유동적인 부가 속성 | jsonb | 자주 JOIN·제약·검색할 핵심 필드는 일반 컬럼부터 검토 |
timestamptz는 시점을 저장하고 세션의 시간대에 맞춰 표시한다. “서울 시간대”라는 이름 자체를
값에 보존하는 타입은 아니다. 예약 장소의 시간대가 필요하면 별도 컬럼을 둔다.
(날짜와 시간)
jsonb도 인덱스를 사용할 수 있지만 모든 관계를 JSON 안에 숨기면 필수 값과 참조 무결성을 관리하기
어려워진다. 스키마가 없다는 뜻으로 쓰지 않는다.
(JSON 타입)
제약이 실제로 막는지 확인하기
섹션 제목: “제약이 실제로 막는지 확인하기”공통 예제의 psql에서 아래 명령은 각각 따로 실행한다. 모두 실패해야 정상이다. 자동 커밋 상태에서 실패한 문장은 데이터를 남기지 않는다.
INSERT INTO public.projects (name) VALUES ('플랫폼');INSERT INTO public.tasks (project_id, title) VALUES (999, '없는 프로젝트');INSERT INTO public.tasks (project_id, title) VALUES (1, '');순서대로 unique·foreign key·check 제약 오류가 나온다. 앱에서만 검사했다면 관리자 SQL이나 다른 서비스의 쓰기가 규칙을 우회할 수 있었지만, DB 제약은 그 경로에서도 적용된다. 실패한 INSERT도 identity 번호를 소비할 수 있다. 번호가 연속인지로 데이터 유실을 판단하지 않는다.
삭제 규칙도 설계다
섹션 제목: “삭제 규칙도 설계다”공통 예제는 작업이 있는 프로젝트를 바로 지우지 못하게 한다. ON DELETE CASCADE를 선택하면
관련 작업도 자동 삭제할 수 있지만, 실수의 영향 범위가 커진다. 프로젝트 보관 처리, 명시적 하위 삭제,
연쇄 삭제 중 업무 의미에 맞는 것을 고른다.
이해 확인: assignee IS NULL인 작업은 데이터 오류일까? 이 예제에서는 미배정 상태이므로 정상이다.
반면 project_id는 모든 작업이 프로젝트에 속해야 해서 NULL을 허용하지 않는다.