범위·다중범위 함수·연산자
범위·다중범위 함수·연산자 (Range/Multirange)
"예약 시간이 겹치는지", "이 가격 구간이 저 구간을 포함하는지" 같은 질문을 풀려면 범위 타입이 제격이에요. PostgreSQL의 범위(range) 타입은 값의 구간을 하나의 값으로 다루게 해 주고, 여기에 더해 범위들을 묶은 다중범위(multirange) 타입까지 있어요. 이 페이지에서 범위·다중범위 전용 연산자와 함수의 핵심을 정리해 볼게요.
출처: PostgreSQL 공식 문서 — Range/Multirange Functions and Operators
포함·겹침 관계 연산자
범위 타입에는 전용 연산자가 있어요. 일반 비교 연산자도 사용 가능한데, 먼저 범위 하한을 기준으로 정렬하고 하한이 같을 때만 상한을 비교해요. (다중범위의 비교 연산자는 각 범위를 하나씩 비교하다 달라지는 지점에서 결정돼요. 이는 일반적으로 유용한 전체 정렬을 만들진 않지만, 범위에 고유 인덱스를 만들 수 있게 해 줘요.)
범위 연산자 중 포함·겹침 관계를 검사하는 것들이 핵심이에요.
| 연산자 | 설명 | 예시 |
|---|---|---|
anyrange @> anyrange → boolean |
첫 범위가 두 번째를 포함하나요? | int4range(2,4) @> int4range(2,3) → t |
anyrange @> anyelement → boolean |
범위가 요소를 포함하나요? | '[2011-01-01,2011-03-01)'::tsrange @> '2011-01-10'::timestamp → t |
anyrange <@ anyrange → boolean |
첫 범위가 두 번째에 포함되나요? | int4range(2,4) <@ int4range(1,7) → t |
anyelement <@ anyrange → boolean |
요소가 범위에 포함되나요? | 42 <@ int4range(1,7) → f |
anyrange && anyrange → boolean |
범위가 겹치나요(공통 요소가 있나요)? | int8range(3,7) && int8range(4,12) → t |
위치·인접·집합 연산자
범위 사이의 위치 관계와 집합 연산도 있어요.
| 연산자 | 설명 | 예시 |
|---|---|---|
anyrange << anyrange → boolean |
첫 범위가 두 번째보다 엄격히 왼쪽인가요? | int8range(1,10) << int8range(100,110) → t |
anyrange >> anyrange → boolean |
첫 범위가 두 번째보다 엄격히 오른쪽인가요? | int8range(50,60) >> int8range(20,30) → t |
anyrange &< anyrange → boolean |
첫 범위가 두 번째 오른쪽까지 뻗지 않나요? | int8range(1,20) &< int8range(18,20) → t |
anyrange &> anyrange → boolean |
첫 범위가 두 번째 왼쪽까지 뻗지 않나요? | int8range(7,20) &> int8range(5,10) → t |
| `anyrange - | - anyrange→boolean` |
범위가 인접하나요? |
anyrange + anyrange → anyrange |
범위의 합집합을 계산해요. 결과가 단일 범위가 되려면 두 범위가 겹치거나 인접해야 해요. | numrange(5,15) + numrange(10,20) → [5,20) |
anyrange * anyrange → anyrange |
범위의 교집합을 계산해요. | int8range(5,15) * int8range(10,20) → [10,15) |
anyrange - anyrange → anyrange |
범위의 차집합을 계산해요. 차집합이 단일 범위가 안 되는 방식으로 두 번째가 첫 번째에 포함되면 안 돼요. | int8range(5,15) - int8range(10,20) → [5,10) |
왼쪽/오른쪽/인접 연산자는 빈 범위나 빈 다중범위가 관여하면 항상 false를 반환해요. 즉 빈 범위는 다른 어떤 범위보다 앞 또는 뒤에 있다고 간주되지 않아요.
그 외의 경우 빈 범위·빈 다중범위는 덧셈 항등원(additive identity)처럼 다뤄져요. 무엇과 합집합해도 그 자신이고, 무엇을 빼도 그 자신이에요. 빈 다중범위는 빈 범위와 정확히 같은 점들을 가져요. 모든 범위는 빈 범위를 포함하고, 모든 다중범위는 원하는 만큼의 빈 범위를 포함해요.
주의: 범위의 합집합·차집합 연산자는 결과가 서로 겹치지 않는 두 부분 범위를 담아야 한다면 실패해요. 그런 범위는 하나로 표현할 수 없기 때문이에요. 하지만 다중범위 인자를 받아 다중범위를 반환하는 별도의 합집합·차집합 연산자는, 인자들이 겹치지 않아도 실패하지 않아요. 그래서 서로 떨어진 범위의 합집합·차집합이 필요하다면, 범위를 먼저 다중범위로 캐스팅해 오류를 피할 수 있어요.
다중범위 연산자도 같은 표현들을 지원해요. anymultirange끼리, 또는 anymultirange와 anyrange(그리고 요소) 조합으로 포함(@>), 포함됨(<@), 겹침(&&), 왼쪽/오른쪽(<</>>), 인접(-|-), 합집합(+), 교집합(*), 차집합(-)을 검사·계산할 수 있어요. 다중범위의 합집합은 겹치거나 인접할 필요가 없어요.
'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange → {[5,10), [15,20)}
'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange → {[10,15)}
'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange → {[5,10), [15,20)}
범위 함수
범위 타입에 쓸 수 있는 함수들이에요.
lower(range)→ 하한을 추출해요(빈 범위거나 하한이 없으면 NULL).lower(numrange(1.1,2.2))→1.1upper(range)→ 상한을 추출해요.upper(numrange(1.1,2.2))→2.2isempty(range)→ 범위가 비어 있나요?isempty(numrange(1.1,2.2))→flower_inc(range)/upper_inc(range)→ 하한/상한이 포함적인가요?lower_inc(numrange(1.1,2.2))→t,upper_inc(numrange(1.1,2.2))→flower_inf(range)→ 하한이 없나요? (-Infinity하한은 false를 반환)lower_inf('(,)'::daterange)→tupper_inf(range)→ 상한이 없나요?upper_inf('(,)'::daterange)→trange_merge(range, range)→ 두 범위를 모두 포함하는 가장 작은 범위.range_merge('[1,2)'::int4range, '[3,4)'::int4range)→[1,4)
다중범위 함수는 lower, upper, isempty, lower_inc, upper_inc, lower_inf, upper_inf, range_merge가 같은 역할을 다중범위에 대해 수행해요. 추가로:
multirange(anyrange)→anymultirange: 주어진 범위만 담긴 다중범위를 반환해요.multirange('[1,2)'::int4range)→{[1,2)}unnest(anymultirange)→setof anyrange: 다중범위를 오름차순 범위 집합으로 펼쳐요.unnest('{[1,2), [3,4)}'::int4multirange)→[1,2) [3,4)
lower_inc, upper_inc, lower_inf, upper_inf 함수는 빈 범위·빈 다중범위에 대해 모두 false를 반환해요.