PostgreSQL 18의 exclusion constraint는 두 행을 비교했을 때 지정한 모든 연산이 TRUE가 되는 조합을 허용하지 않습니다. 예약 예제에서는 방 번호가 같고 시간 범위가 겹치는 두 조건이 동시에 참이면 새 행을 거부합니다.
범위의 && 연산자는 공통 시간 요소가 있어 겹치는지 확인합니다. text 방 이름의 GiST 동등 비교에는 btree_gist operator class가 필요하며, [시작, 끝) 반개구간을 쓰면 한 예약이 끝나는 순간 시작하는 다음 예약은 겹치지 않습니다.
같은 공간과 겹치는 시간 조건을 함께 막습니다
행 하나의 CHECK는 그 행에 적힌 값으로 조건을 검사합니다. 하지만 회의실 예약처럼 새 행이 기존 행과 충돌하는지 확인하는 문제는 행 하나만 보고 해결할 수 없습니다. PostgreSQL의 EXCLUDE 제약은 지정한 비교 연산 결과들이 같은 두 행에서 동시에 모두 TRUE가 되는 상황을 허용하지 않습니다. 예약 규칙을 ‘방이 같지 않거나, 시간이 겹치지 않아야 한다’로 표현할 수 있습니다.
이 예제는 room_id WITH =와 during WITH &&를 함께 지정합니다. 같은 room_id면 동등 비교가 참이고, 예약 기간에 공통 시간이 있으면 &&도 참입니다. 두 결과가 모두 참일 때만 EXCLUDE가 중복 예약을 막습니다. 다른 방에서 같은 시간에 예약하거나, 같은 방에서 시간이 겹치지 않으면 비교 하나가 거짓이 되어 제약을 만족합니다.
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE reservation (
room_id text NOT NULL,
during tstzrange NOT NULL,
EXCLUDE USING gist (
room_id WITH =,
during WITH &&
)
);| 기존 예약 | 새 예약 | room_id = | during && | 허용 여부 |
|---|---|---|---|---|
| 회의실 A, 10:00–11:00 | 회의실 A, 10:30–11:30 | TRUE | TRUE | 거부 |
| 회의실 A, 10:00–11:00 | 회의실 B, 10:30–11:30 | FALSE | TRUE | 허용 |
| 회의실 A, 10:00–11:00 | 회의실 A, 11:00–12:00 | TRUE | FALSE | 허용 |
11시에 끝나고 11시에 시작하는 예약은 반개구간에서 맞닿습니다
시간 범위를 [10:00, 11:00)으로 기록하면 10시를 포함하고 11시는 포함하지 않습니다. 다음 예약 [11:00, 12:00)은 11시에 시작하지만 첫 범위에는 11시가 들어 있지 않습니다. 두 범위는 공통 시간점이 없으므로 &&는 거짓입니다. 반면 [10:00, 11:00]처럼 끝 경계를 포함시키고 다음 범위도 11시를 포함시키면 경계 시각 하나가 공통으로 들어 겹침 판정이 달라질 수 있습니다. 예약 시스템에서 끝 경계의 포함 여부를 먼저 정하는 이유입니다.
PostgreSQL 18 문서는 range 타입 예제로 tsrange와 tstzrange를 제공하고, &&가 범위에 공통 요소가 있는지 판정한다고 설명합니다. tstzrange는 시간대 정보가 있는 timestamp를 사용하므로 실제 예약 시스템의 시간대 규칙에 더 가까울 수 있습니다. 이 글은 시간대 변환이나 서머타임 정책을 다루지 않고, 두 범위의 겹침만 추적합니다.
EXCLUDE의 논리를 표에서 다시 읽어 보세요. 첫 행은 같은 방이면서 시간도 겹쳐 두 비교가 참이므로 거부됩니다. 두 번째 행은 시간은 겹쳐도 방이 다르므로 첫 비교가 거짓입니다. 세 번째 행은 방은 같지만 구간이 맞닿기만 하여 겹침이 거짓입니다. 이 방식은 ‘두 개의 조건을 모두 금지한다’가 아니라 ‘두 비교가 한 쌍에서 동시에 참인 상태를 금지한다’는 규칙입니다.
범위 경계와 비교 연산을 먼저 정하고 표를 채웁니다
- 같은 공간을 뜻하는 키 값과 범위 열을 확인합니다.
- 방 비교에
=를, 시간 범위 비교에&&를 쓴다는 제약식을 읽습니다. - 후보 행과 기존 행 각각에 대해 room 동등 여부와 range overlap 여부를 계산합니다.
- 두 비교가 모두 참이면 exclusion violation으로 판정합니다.
- 경계가 맞닿은 두 예약은
[시작, 끝)의 포함 규칙을 적용해 겹침을 별도로 계산합니다. - text 등 단순 키의 GiST 연산자 클래스가 필요하면 btree_gist 같은 제공 모듈과 설치 권한을 확인합니다.
btree_gist는 PostgreSQL에 함께 제공되는 extension이지만 모든 데이터베이스에서 자동으로 활성화된다고 가정하면 안 됩니다. 예제를 적용하려면 해당 서버에 extension이 설치되어 있고 CREATE EXTENSION을 수행할 권한이 있어야 합니다. 운영 환경에서 이 권한이 없으면 DBA 절차가 필요할 수 있습니다. 여기서는 DDL 문법을 읽기 위한 설명만 하며, 실제 데이터베이스에 extension이나 테이블을 만들지 않습니다.
CHECK 하나로 다른 예약 행을 검사한다고 가정하지 않습니다
일반적인 오답은 CHECK (start_time < end_time)가 예약 간 충돌도 검사한다고 생각하는 것입니다. 그 CHECK는 한 행 안에서 시작과 끝의 순서만 확인합니다. 다른 행에 같은 방과 겹치는 기간이 있는지는 비교하지 않습니다. 반대 실수는 EXCLUDE가 한 행의 잘못된 범위까지 자동 수정한다고 여기는 것입니다. 범위 자체의 유효한 업무 규칙도 필요하면 별도로 표현해야 합니다.
또한 exclusion constraint는 PostgreSQL 고유 기능과 GiST 인덱스 방식을 포함하므로 모든 SQL 데이터베이스에서 그대로 쓸 수 있는 일반 SQL 문법이 아닙니다. 이 글의 SQL은 PostgreSQL 18 공식 문서를 바탕으로 만든 창작 예시이며, 실제 서버 실행으로 성능이나 extension 상태를 확인하지 않았습니다.
정답을 가리고 확인해 보세요
이 글을 위해 만든 학습 예시이며 공식 기출문제나 판매 카드의 인용이 아닙니다.
PostgreSQL 18의 EXCLUDE (room_id WITH =, during WITH &&)에서 방 A의 [14:00,15:00)과 방 A의 [15:00,16:00)은 충돌하나요?
정답과 해설 확인
충돌하지 않습니다. room_id 동등 비교는 TRUE지만 반개구간이 15:00에서만 맞닿아 공통 시간 요소가 없으므로 during &&는 FALSE입니다. 둘 중 하나가 FALSE여서 EXCLUDE가 허용합니다.PostgreSQL 18 range 연산 문서에서 &&는 공통 원소가 있는지 검사합니다. EXCLUDE는 지정 비교 중 적어도 하나가 false 또는 null인 두 행을 허용합니다.