범위·다중범위 함수·연산자

범위·다중범위 함수·연산자 (Range/Multirange)

"예약 시간이 겹치는지", "이 가격 구간이 저 구간을 포함하는지" 같은 질문을 풀려면 범위 타입이 제격이에요. PostgreSQL의 범위(range) 타입은 값의 구간을 하나의 값으로 다루게 해 주고, 여기에 더해 범위들을 묶은 다중범위(multirange) 타입까지 있어요. 이 페이지에서 범위·다중범위 전용 연산자와 함수의 핵심을 정리해 볼게요.

출처: PostgreSQL 공식 문서 — Range/Multirange Functions and Operators

포함·겹침 관계 연산자

범위 타입에는 전용 연산자가 있어요. 일반 비교 연산자도 사용 가능한데, 먼저 범위 하한을 기준으로 정렬하고 하한이 같을 때만 상한을 비교해요. (다중범위의 비교 연산자는 각 범위를 하나씩 비교하다 달라지는 지점에서 결정돼요. 이는 일반적으로 유용한 전체 정렬을 만들진 않지만, 범위에 고유 인덱스를 만들 수 있게 해 줘요.)

범위 연산자 중 포함·겹침 관계를 검사하는 것들이 핵심이에요.

연산자 설명 예시
anyrange @> anyrangeboolean 첫 범위가 두 번째를 포함하나요? int4range(2,4) @> int4range(2,3)t
anyrange @> anyelementboolean 범위가 요소를 포함하나요? '[2011-01-01,2011-03-01)'::tsrange @> '2011-01-10'::timestampt
anyrange <@ anyrangeboolean 첫 범위가 두 번째에 포함되나요? int4range(2,4) <@ int4range(1,7)t
anyelement <@ anyrangeboolean 요소가 범위에 포함되나요? 42 <@ int4range(1,7)f
anyrange && anyrangeboolean 범위가 겹치나요(공통 요소가 있나요)? int8range(3,7) && int8range(4,12)t

위치·인접·집합 연산자

범위 사이의 위치 관계와 집합 연산도 있어요.

연산자 설명 예시
anyrange << anyrangeboolean 첫 범위가 두 번째보다 엄격히 왼쪽인가요? int8range(1,10) << int8range(100,110)t
anyrange >> anyrangeboolean 첫 범위가 두 번째보다 엄격히 오른쪽인가요? int8range(50,60) >> int8range(20,30)t
anyrange &< anyrangeboolean 첫 범위가 두 번째 오른쪽까지 뻗지 않나요? int8range(1,20) &< int8range(18,20)t
anyrange &> anyrangeboolean 첫 범위가 두 번째 왼쪽까지 뻗지 않나요? int8range(7,20) &> int8range(5,10)t
`anyrange - - anyrangeboolean` 범위가 인접하나요?
anyrange + anyrangeanyrange 범위의 합집합을 계산해요. 결과가 단일 범위가 되려면 두 범위가 겹치거나 인접해야 해요. numrange(5,15) + numrange(10,20)[5,20)
anyrange * anyrangeanyrange 범위의 교집합을 계산해요. int8range(5,15) * int8range(10,20)[10,15)
anyrange - anyrangeanyrange 범위의 차집합을 계산해요. 차집합이 단일 범위가 안 되는 방식으로 두 번째가 첫 번째에 포함되면 안 돼요. int8range(5,15) - int8range(10,20)[5,10)

왼쪽/오른쪽/인접 연산자는 빈 범위나 빈 다중범위가 관여하면 항상 false를 반환해요. 즉 빈 범위는 다른 어떤 범위보다 앞 또는 뒤에 있다고 간주되지 않아요.

그 외의 경우 빈 범위·빈 다중범위는 덧셈 항등원(additive identity)처럼 다뤄져요. 무엇과 합집합해도 그 자신이고, 무엇을 빼도 그 자신이에요. 빈 다중범위는 빈 범위와 정확히 같은 점들을 가져요. 모든 범위는 빈 범위를 포함하고, 모든 다중범위는 원하는 만큼의 빈 범위를 포함해요.

주의: 범위의 합집합·차집합 연산자는 결과가 서로 겹치지 않는 두 부분 범위를 담아야 한다면 실패해요. 그런 범위는 하나로 표현할 수 없기 때문이에요. 하지만 다중범위 인자를 받아 다중범위를 반환하는 별도의 합집합·차집합 연산자는, 인자들이 겹치지 않아도 실패하지 않아요. 그래서 서로 떨어진 범위의 합집합·차집합이 필요하다면, 범위를 먼저 다중범위로 캐스팅해 오류를 피할 수 있어요.

다중범위 연산자도 같은 표현들을 지원해요. anymultirange끼리, 또는 anymultirangeanyrange(그리고 요소) 조합으로 포함(@>), 포함됨(<@), 겹침(&&), 왼쪽/오른쪽(<</>>), 인접(-|-), 합집합(+), 교집합(*), 차집합(-)을 검사·계산할 수 있어요. 다중범위의 합집합은 겹치거나 인접할 필요가 없어요.

'{[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.1
  • upper(range) → 상한을 추출해요. upper(numrange(1.1,2.2))2.2
  • isempty(range) → 범위가 비어 있나요? isempty(numrange(1.1,2.2))f
  • lower_inc(range) / upper_inc(range) → 하한/상한이 포함적인가요? lower_inc(numrange(1.1,2.2))t, upper_inc(numrange(1.1,2.2))f
  • lower_inf(range) → 하한이 없나요? (-Infinity 하한은 false를 반환) lower_inf('(,)'::daterange)t
  • upper_inf(range) → 상한이 없나요? upper_inf('(,)'::daterange)t
  • range_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를 반환해요.

더 알아보기 (Learn more)