명령어/DB

[PostgreSQL] range 반열림 구간 [) — 인접 기간이 안 겹치는 이유

jykim23 2026. 7. 22. 23:18
반응형

설치·접속: PostgreSQL 설치와 접속

부제: 구독 기간을 [시작, 종료) 로 저장했더니 종료일과 다음 구독 시작일이 딱 붙어도 겹침 판정이 안 나야 할 때

-- range 는 무조건 반열림 [) 표준형으로 정규화된다
SELECT '[3,7]'::int4range AS closed,   -- 7 포함 -> [3,8)
       '[3,7)'::int4range AS half,     -- [3,7)
       '(3,7)'::int4range AS open;     -- 3 제외 -> [4,7)

-- 인접 구간은 겹치지 않는다 (경계값 공유해도)
SELECT int4range(10,20) && int4range(20,30) AS overlap,     -- 겹침?
       int4range(10,20) -|- int4range(20,30) AS adjacent;   -- 인접?

-- [4,4) 는 빈 구간으로 정규화
SELECT '[4,4)'::int4range AS r,
       isempty('[4,4)'::int4range) AS is_empty,
       '[4,4]'::int4range AS one_point;  -- 점 하나 -> [4,5)
 closed | half  | open  
--------+-------+-------
 [3,8)  | [3,7) | [4,7)

 overlap | adjacent 
---------+----------
 f       | t

   r   | is_empty | one_point 
-------+----------+-----------
 empty | t        | [4,5)

range 타입은 어떻게 입력하든 하한 포함·상한 제외 [) 형태로 정규화한다. [3,7](양끝 포함)은 [3,8)로, (3,7)(양끝 제외)은 [4,7)로 바뀐다. 그래서 [10,20)[20,30)은 경계값 20을 공유하지만 20이 앞 구간엔 안 들어가므로 &&(겹침)은 거짓, -|-(인접)만 참이다. 대부분 "시작~끝을 둘 다 포함"으로 생각해서 이 지점에서 어긋난다. 반열림이 표준형이라 인접 기간을 이어 붙일 때 경계 하루가 이중 계산되지 않는다.

빈 구간 규칙도 여기서 나온다. [4,4)는 하한은 포함인데 상한 4 미만이라 담을 값이 없어 empty로 정규화되고 isempty가 참이다. 반면 [4,4]는 4 하나를 담은 [4,5)가 된다. 구독·예약 기간을 [start, end)로 저장하면 종료일과 다음 시작일이 같은 날이어도 겹침이 안 나서 인접 처리가 깔끔해진다 — 아래 구독 테이블에서 세 기간이 연달아 붙어도 겹치는 쌍이 하나도 없다.

CREATE TABLE sub (user_id int, period daterange);
INSERT INTO sub VALUES
 (1, '[2026-01-01,2026-02-01)'),
 (1, '[2026-02-01,2026-03-01)'),
 (1, '[2026-03-01,2026-04-01)');

SELECT a.period, b.period, a.period && b.period AS overlap
FROM sub a JOIN sub b ON a.ctid < b.ctid;
          period         |          period         | overlap 
-------------------------+-------------------------+---------
 [2026-01-01,2026-02-01) | [2026-02-01,2026-03-01) | f
 [2026-01-01,2026-02-01) | [2026-03-01,2026-04-01) | f
 [2026-02-01,2026-03-01) | [2026-03-01,2026-04-01) | f

이렇게도 쓴다

@> 포함: 특정 값/구간이 범위 안에 있나.

SELECT int4range(1,20) @> 5 AS contains_5;  -- t

 

* 교집합: 두 구간의 겹치는 부분만.

SELECT int4range(1,20) * int4range(10,30) AS intersect;  -- [10,20)

 

lower / upper 로 경계값 추출.

SELECT lower(int4range(10,30)) AS lo, upper(int4range(10,30)) AS hi;  -- 10, 30

 

무한 경계: 종료일 없는 "현재 진행 중" 구독.

SELECT '[2026-07-01,)'::daterange @> current_date AS active_now;

 

겹침 삽입을 DB가 원천 차단. (조합: EXCLUDE + GiST)

CREATE TABLE booking (room text, during tsrange,
  EXCLUDE USING GIST (during WITH &&));

 

빈 구간 방어: 잘못된 입력을 CHECK 로 거른다.

ALTER TABLE sub ADD CHECK (NOT isempty(period));
반응형