[SQL 실습 #4] 병원 예약 시스템 데이터 모델 작성
UNIQUE 제약 조건으로 예약 충돌을 원천 차단합니다. 같은 의사, 같은 시간에 중복 예약 불가 규칙을 데이터베이스 레벨에서 강제하는 방법을 배웁니다.
시작 전, 워밍업
병원 예약 앱에서 같은 시간에 두 명이 예약되면 어떻게 될까?
오전 10시, 김 의사 예약이 이미 있는데 또 다른 환자가 같은 시간에 예약.
이런 충돌을 막으려면 코드에서 일일이 체크해야 할까?
시작하며 - 충돌 방지의 중요성
지금까지 학생 정보, 도서관 대출, 주차 관리 시스템을 만들어봤습니다.
이번엔 좀 더 복잡한 비즈니스 로직이 필요한 시스템을 다뤄봅니다.
병원 예약 시스템입니다.
"같은 시간에 같은 의사에게 2명이 예약할 수 없다."
이 단순한 규칙을 데이터베이스로 어떻게 강제할 수 있을까요?
답은 UNIQUE 제약 조건입니다.
데이터베이스 레벨에서 충돌을 원천적으로 차단하는 방법을 배워봅시다.
ERD 설계하기
병원 예약 시스템에 필요한 것들:
- doctors (의사) - 의사 정보
- patients (환자) - 환자 정보
- appointments (예약) - 예약 기록
테이블 구조
1. doctors 테이블
- doctor_id (의사 ID) - Primary Key
- name (이름)
- specialty (진료과목: 내과, 외과, 안과 등)
2. patients 테이블
- patient_id (환자 ID) - Primary Key
- name (이름)
- phone (전화번호)
- birth_date (생년월일)
3. appointments 테이블
- appointment_id (예약 ID) - Primary Key
- doctor_id (의사 ID) - Foreign Key → doctors.doctor_id
- patient_id (환자 ID) - Foreign Key → patients.patient_id
- appointment_time (예약 시간) - DATETIME
- status (상태: scheduled, completed, cancelled)
- ⚠️ UNIQUE(doctor_id, appointment_time) - 핵심 제약!
병원 예약 시스템 ERD - UNIQUE 제약으로 예약 충돌 방지
UNIQUE 제약 조건이란?
특정 컬럼 조합이 중복될 수 없도록 강제하는 규칙입니다.
UNIQUE(doctor_id, appointment_time)는
"같은 의사, 같은 시간"에 중복 예약을 원천 차단합니다.
테이블 생성하기
이제 3개의 테이블을 차례대로 만들어봅시다.
1. doctors 테이블 생성
CREATE TABLE doctors (
doctor_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
specialty TEXT NOT NULL
);
의사 테이블 생성
2. patients 테이블 생성
CREATE TABLE patients (
patient_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
phone TEXT,
birth_date DATE
);
환자 테이블 생성
3. appointments 테이블 생성 (UNIQUE 제약 포함)
CREATE TABLE appointments (
appointment_id INTEGER PRIMARY KEY,
doctor_id INTEGER NOT NULL,
patient_id INTEGER NOT NULL,
appointment_time DATETIME NOT NULL,
status TEXT DEFAULT 'scheduled',
FOREIGN KEY (doctor_id) REFERENCES doctors(doctor_id),
FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
UNIQUE(doctor_id, appointment_time)
);
코드 설명:
FOREIGN KEY (doctor_id) REFERENCES doctors(doctor_id)- doctors 테이블 참조FOREIGN KEY (patient_id) REFERENCES patients(patient_id)- patients 테이블 참조UNIQUE(doctor_id, appointment_time)- 같은 의사, 같은 시간에 중복 예약 불가
예약 테이블 생성 - UNIQUE 제약으로 동시 예약 방지
데이터 입력하기
1. doctors 데이터 입력
INSERT INTO doctors (doctor_id, name, specialty)
VALUES
(1, '김내과', '내과'),
(2, '이외과', '외과'),
(3, '박안과', '안과'),
(4, '최치과', '치과');
의사 4명 데이터 INSERT 쿼리 실행
Browse Data로 입력된 의사 4명 확인
2. patients 데이터 입력
INSERT INTO patients (patient_id, name, phone, birth_date)
VALUES
(1, '홍길동', '010-1234-5678', '1990-05-15'),
(2, '김영희', '010-2345-6789', '1985-03-22'),
(3, '이철수', '010-3456-7890', '1995-11-30'),
(4, '박민수', '010-4567-8901', '2000-01-10'),
(5, '최수진', '010-5678-9012', '1988-07-25');
환자 5명 데이터 INSERT 쿼리 실행
Browse Data로 입력된 환자 5명 확인
3. appointments 데이터 입력 (정상 케이스)
INSERT INTO appointments (appointment_id, doctor_id, patient_id, appointment_time, status)
VALUES
(1, 1, 1, '2026-02-10 09:00:00', 'scheduled'),
(2, 1, 2, '2026-02-10 10:00:00', 'scheduled'),
(3, 2, 3, '2026-02-10 09:00:00', 'scheduled'),
(4, 3, 4, '2026-02-10 14:00:00', 'scheduled'),
(5, 1, 5, '2026-02-10 11:00:00', 'scheduled');
데이터 설명:
- 예약 1: 김내과 의사가 2/10 09시에 홍길동 환자 진료
- 예약 2: 김내과 의사가 2/10 10시에 김영희 환자 진료 (시간이 다르므로 OK)
- 예약 3: 이외과 의사가 2/10 09시에 이철수 환자 진료 (의사가 다르므로 OK)
- 예약 4: 박안과 의사가 2/10 14시에 박민수 환자 진료
- 예약 5: 김내과 의사가 2/10 11시에 최수진 환자 진료
예약 5건 INSERT 쿼리 실행
Browse Data로 입력된 예약 5건 확인
UNIQUE 제약 테스트 - 충돌 시도!
이제 정말 UNIQUE 제약이 작동하는지 확인해봅시다.
일부러 충돌을 일으켜보겠습니다.
실패 예제: 같은 의사, 같은 시간에 중복 예약 시도
INSERT INTO appointments (appointment_id, doctor_id, patient_id, appointment_time, status)
VALUES
(6, 1, 3, '2026-02-10 09:00:00', 'scheduled');
위 쿼리는 실패합니다!
왜냐하면:
- doctor_id = 1 (김내과 의사)
- appointment_time = '2026-02-10 09:00:00'
- 이미 예약 1에서 김내과 의사가 2/10 09시에 예약되어 있음
UNIQUE 제약 위반 - 김내과 의사, 2/10 09:00에 중복 예약 시도 실패
에러 메시지:
Error: UNIQUE constraint failed: appointments.doctor_id, appointments.appointment_time
이게 바로 데이터베이스의 힘입니다!
애플리케이션 코드에서 체크하는 게 아니라,
데이터베이스 레벨에서 원천 차단합니다.
어떤 프로그램, 어떤 사용자가 접근해도 이 규칙은 절대 깨지지 않습니다.
JOIN - 예약 현황 조회
이제 실전 쿼리를 만들어봅시다.
"누가 어느 의사에게 언제 예약했는지" 한눈에 보는 쿼리입니다.
SELECT
doctors.name AS 의사명,
doctors.specialty AS 진료과,
patients.name AS 환자명,
appointments.appointment_time AS 예약시간,
appointments.status AS 상태
FROM appointments
JOIN doctors ON appointments.doctor_id = doctors.doctor_id
JOIN patients ON appointments.patient_id = patients.patient_id
ORDER BY appointments.appointment_time;
3개 테이블을 JOIN하여 예약 현황 조회 - 의사명, 전문과, 환자명, 예약시간, 상태
보시다시피 3개의 테이블을 연결해서 한눈에 예약 현황을 파악할 수 있습니다.
김내과 의사가 09:00, 10:00, 11:00에 3명의 환자를 진료하고,
이외과 의사는 09:00에, 박안과 의사는 14:00에 진료합니다.
GROUP BY - 의사별 예약 건수 집계
"어느 의사가 가장 바쁜가?" 알아보는 쿼리입니다.
SELECT
doctors.name AS 의사명,
COUNT(*) AS 예약건수
FROM appointments
JOIN doctors ON appointments.doctor_id = doctors.doctor_id
GROUP BY doctors.doctor_id, doctors.name
ORDER BY 예약건수 DESC;
의사별 예약 건수 집계 - 김내과 3건, 이외과 1건, 박안과 1건
GROUP BY는 같은 값끼리 묶어서 집계할 때 사용합니다.
COUNT(*)는 각 그룹의 행 개수를 세는 함수예요.
김내과 의사가 3건으로 가장 많은 예약을 받았네요!
축하합니다!
여러분은 비즈니스 로직이 있는 데이터베이스를 만들었습니다.
배운 내용 정리:
- ✅ UNIQUE 제약 조건 - 특정 컬럼 조합 중복 방지
- ✅ 데이터베이스 레벨 검증 - 애플리케이션 코드 없이도 규칙 강제
- ✅ 복잡한 JOIN - 3개 테이블 연결
- ✅ GROUP BY - 데이터 그룹화 및 집계
- ✅ 실전 비즈니스 로직 - 예약 충돌 방지
실생활 응용
이 패턴은 다양한 예약 시스템에 적용됩니다:
- 회의실 예약: UNIQUE(room_id, time_slot)
- 렌터카 예약: UNIQUE(vehicle_id, rental_date)
- 스터디룸 예약: UNIQUE(room_id, date, time)
브리딩 관리라면?
UNIQUE(male_id, breeding_date)로
"같은 수컷이 같은 날 여러 교배 불가" 규칙을 강제할 수 있어요.
다음 글 예고
다음 실습이 마지막입니다!
음악 스트리밍 서비스를 만들어봅니다.
이번엔 N:M 관계를 배웁니다.
"한 플레이리스트에 여러 곡, 한 곡이 여러 플레이리스트에"
이런 복잡한 관계를 데이터베이스로 어떻게 표현할까요?
SQL 시리즈의 대미를 장식할 마지막 실습, 기대하세요! 👋
