관계형 데이터 모델과 데이터 무결성
2강. 관계형 데이터 모델과 데이터 무결성
1. 이번 강의에서 해결할 문제
랭킹 점수와 플레이어 이름을 한 행에 반복 저장하면 이름 변경과 집계 결과가 서로 어긋납니다. 관계형 모델의 구성 요소를 이용해 중복과 모순을 줄이고 어떤 상태를 허용할지 선언합니다.
2. 학습 목표
3. 핵심 개념
테이블은 릴레이션의 구현이며 행은 한 사실, 열은 그 사실의 속성을 나타냅니다. 기본 키는 행을 식별하고 외래 키는 다른 릴레이션의 존재를 참조합니다. 자료형, NOT NULL, CHECK는 값의 도메인을 좁힙니다. 무결성은 “정상 상태가 무엇인가”를 DBMS가 판별할 수 있게 만드는 규칙입니다.
관계형 모델은 단순히 데이터를 표 모양으로 보여 주는 방식이 아닙니다. 한 행이 어떤 사실을 나타내는지 정하고, 열에 들어갈 값의 범위와 행 사이 연결을 규칙으로 선언합니다. 그래서 잘못된 입력이 들어왔을 때 나중에 정리하는 대신 저장 순간에 거부할 수 있습니다.
용어를 랭킹 데이터에 대응하기
| 관계형 용어 | 구현에서 보이는 것 | 예 |
|---|---|---|
| 릴레이션(relation) | 테이블 | ranking.players |
| 튜플(tuple) | 한 행 | 플레이어 42의 계정 기록 |
| 속성(attribute) | 열 | nickname, rating |
| 도메인(domain) | 한 속성이 가질 수 있는 값의 범위 | rating은 0 이상의 정수 |
| 후보 키 | 행을 유일하게 찾을 수 있는 속성 집합 | player_id, 현재 규칙에서는 nickname |
| 기본 키 | 후보 키 중 대표 식별자로 선택한 키 | player_id |
| 외래 키 | 다른 릴레이션의 키를 참조하는 값 | 스냅샷의 player_id |
세 종류의 무결성
- 엔터티 무결성: 각 행을 기본 키로 유일하게 식별하고 기본 키가 비어 있지 않아야 합니다.
- 참조 무결성: 외래 키가 실제로 존재하는 부모 행을 가리켜야 합니다.
- 도메인 무결성: 열의 값이 자료형, 필수 여부, 범위 같은 업무 규칙을 만족해야 합니다.
예를 들어 player_id = 7인 플레이어가 없는데 그 플레이어의 스냅샷을 저장하면 참조 무결성이 깨집니다. rating = -100은 존재하는 플레이어의 행이라도 도메인 규칙을 깨뜨립니다.
4. 단계별 예시
파일 경로: C:\dev\game-ranking-service\database\sql\02-integrity.sql
실행 전에는 ranking 스키마와 두 테이블이 없다고 가정합니다. 실행 후에는 플레이어를 먼저 만들고 그 식별자를 참조하는 랭킹 스냅샷을 저장할 수 있습니다.
CREATE SCHEMA IF NOT EXISTS ranking;
CREATE TABLE ranking.players (
player_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nickname varchar(30) NOT NULL UNIQUE,
rating integer NOT NULL DEFAULT 1000 CHECK (rating >= 0)
);
CREATE TABLE ranking.ranking_snapshots (
snapshot_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
player_id bigint NOT NULL REFERENCES ranking.players(player_id),
rank_position integer NOT NULL CHECK (rank_position > 0),
rating integer NOT NULL CHECK (rating >= 0),
recorded_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (player_id, recorded_at)
);
중요한 선언을 한 줄씩 이해하기
CREATE SCHEMA IF NOT EXISTS ranking은 관련 객체를 담을 네임스페이스를 준비합니다.GENERATED ALWAYS AS IDENTITY는 DBMS가 새 식별값을 생성하게 합니다. 클라이언트가 임의의 ID를 고르지 않습니다.PRIMARY KEY는player_id와snapshot_id의 중복과 NULL을 막습니다.nickname ... NOT NULL UNIQUE는 닉네임 누락과 중복을 거부하지만, 변경 가능한 닉네임을 기본 키로 삼지는 않습니다.DEFAULT 1000은 rating을 생략했을 때 시작값을 정하고,CHECK (rating >= 0)은 음수를 거부합니다.REFERENCES ranking.players(player_id)는 존재하는 플레이어의 ID만 스냅샷에 허용합니다.UNIQUE (player_id, recorded_at)은 같은 플레이어와 같은 기록 시각의 중복 조합을 막습니다.
Set-Location C:\dev\game-ranking-service
psql -d game_ranking_dev -v ON_ERROR_STOP=1 -f .\database\sql\02-integrity.sql
정상 입력과 실패 입력 비교
먼저 정상 플레이어와 스냅샷을 넣습니다.
INSERT INTO ranking.players (nickname) VALUES ('mira') RETURNING player_id, rating;
INSERT INTO ranking.ranking_snapshots (player_id, rank_position, rating)
VALUES (1, 1, 1200);
개발 DB가 새 상태라면 첫 결과는 새 player_id와 기본 평점 1000을 보여 줍니다. 실제 ID는 기존 행 수에 따라 1이 아닐 수 있으므로 RETURNING 결과를 사용해야 합니다.
다음 두 입력은 실패해야 정상입니다.
-- 도메인 무결성 위반: 평점이 음수
INSERT INTO ranking.players (nickname, rating) VALUES ('broken-rating', -1);
-- 참조 무결성 위반: 존재하지 않는 플레이어 참조
INSERT INTO ranking.ranking_snapshots (player_id, rank_position, rating)
VALUES (999999, 1, 1200);
오류가 발생하면 먼저 제약 이름과 대상 열을 읽습니다. 제약을 지우기 전에 입력이 업무 규칙을 어긴 것인지 확인합니다.
5. 동작 원리
PRIMARY KEY는 식별값의 중복과 NULL을 막습니다. FOREIGN KEY는 존재하지 않는 플레이어의 랭킹 기록을 거부합니다. UNIQUE는 같은 시각에 같은 플레이어의 스냅샷이 중복되는 것을 막고 CHECK는 음수 평점과 0등을 거부합니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
8. 이해 점검 질문 3개
9. 핵심 요약
다음 강의 연결
무결성 규칙이 들어갈 객체를 만들었으므로 다음에는 PostgreSQL에서 데이터베이스·스키마·테이블이 어느 경계에 놓이는지 확인합니다. 3강. 데이터베이스·스키마·테이블의 계층으로 이어갑니다.
관계형 데이터 모델과 데이터 무결성 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.