Appearance
Диапазонные типы данных
Диапазонные типы представляют диапазоны значений некоторого типа данных (он также называется подтипом диапазона). Например, диапазон типа timestamp может представлять временной интервал, когда зарезервирован зал заседаний. В данном случае типом данных будет tsrange (сокращение от «timestamp range»), а подтипом — timestamp. Подтип должен быть полностью упорядочиваемым, чтобы можно было однозначно определить, где находится значение по отношению к диапазону: внутри, до или после него.
Диапазонные типы полезны тем, что позволяют представить множество возможных значений в одной структуре данных и чётко выразить такие понятия, как пересечение диапазонов. Наиболее очевидный вариант их использования — применять диапазоны даты и времени для составления расписания, но также полезными могут оказаться диапазоны цен, интервалы измерений и т. д.
ВАЖНО: Один диапазон представляет единый непрерывный интервал без внутренних разрывов. Для хранения нескольких разрывных интервалов используются мультидиапазонные типы (PostgreSQL 14+).
Встроенные диапазонные типы
PostgreSQL предоставляет несколько встроенных диапазонных типов:
sql
-- Целочисленные диапазоны
int4range — диапазон integer
int8range — диапазон bigint
-- Числовые диапазоны с плавающей точкой
numrange — диапазон numeric
-- Временные диапазоны
tsrange — диапазон timestamp без временной зоны
tstzrange — диапазон timestamp с временной зоной
daterange — диапазон dateВключение и исключение границ
Любой непустой диапазон имеет две границы, верхнюю и нижнюю, и включает все точки между этими значениями. В него также может входить точка, лежащая на границе, если диапазон включает эту границу. И наоборот, если диапазон не включает границу, считается, что точка, лежащая на этой границе, в него не входит.
В текстовой записи диапазона включение нижней границы обозначается символом «[», а исключением — символом «(». Для верхней границы включение обозначается аналогично, символом «]», а исключение — символом «)».
Для проверки, включается ли нижняя или верхняя граница в диапазон, предназначены функции lower_inc и upper_inc, соответственно.
Неограниченные (бесконечные) диапазоны
Нижнюю границу диапазона можно опустить и определить тем самым диапазон, включающий все значения, лежащие ниже верхней границы, например: (,3]. Подобным образом, если не определить верхнюю границу, в диапазон войдут все значения, лежащие выше нижней границы. Если же опущена и нижняя, и верхняя границы, такой диапазон будет включать все возможные значения своего подтипа. Указание отсутствующей границы как включаемой в диапазон автоматически преобразуется в исключающее; например, [,] преобразуется в (,). Можно воспринимать отсутствующие значения как плюс/минус бесконечность, но всё же это особые значения диапазонного типа, которые охватывают и возможные для подтипа значения плюс/минус бесконечность.
Для подтипов, в которых есть понятие «бесконечность», infinity может использоваться в качестве явного значения границы. При этом, например, в диапазон [today,infinity) с подтипом timestamp не будет входит специальное значение infinity данного подтипа, однако это значение будет входить в диапазон [today,infinity], как и в диапазоны [today,) и [today,].
Проверить, определена ли верхняя или нижняя граница, можно с помощью функций lower_inf и upper_inf, соответственно.
Базовое создание диапазонов
sql
-- создание диапазонов различными способами
select '[1,10]'::int4range; -- от 1 включительно до 10 включительно
select '(1,10)'::int4range; -- от 1 не включительно до 10 не включительно
select '[2023-01-01,2023-12-31]'::daterange; -- от 2023-01-01 включительно до 2023-12-31 включительно
select '[100,500)'::numrange; -- от 100 включительно до 500 не включительноСоздание таблиц с диапазонными типами
sql
-- таблица для проектов
create table projects (
id serial primary key,
name varchar(100),
execution_period tstzrange, --период реализации проекта
budget_range numrange --диапазон бюджетной стоимости проекта
);EXCLUDE
Ограничение-исключение, которое позволяет определить условие «непересечения» диапазонов.
sql
-- таблица для бронирования номеров
create table hotel_reservations (
id serial primary key,
room_number integer,
reservation_period daterange, --период резерва номера
price_range numrange, --диапазон стоимости аренды
exclude using gist (room_number with =, reservation_period with &&)
--проверяем, что один и тот же номер нельзя арендовать при пересечении диапазонов аренды
);
/*
Так как для типа integer по умолчанию не определен класс операторов GiST, то
нужно установить расширение btree_gist, которое добавляет поддержку GiST индексов
для обычных скалярных типов данных:
*/
create extension if not exists btree_gist;Важно: EXCLUDE особенно важен в многопользовательских системах, так как гарантирует отсутствие пересекающихся диапазонов даже при конкурентных INSERT/UPDATE.
Функции для работы с диапазонами
sql
-- получение границ диапазона
select lower('[1,10)'::int4range), upper('[1,10)'::int4range);text
lower|upper|
-----+-----+
1| 10|sql
-- проверка пустоты
select isempty('(1,1)'::int4range); -- true
-- извлечение границ
select lower_inc('[1,10]'::int4range), upper_inc('[1,10]'::int4range);text
lower_inc|upper_inc|
---------+---------+
true |false |
--тип данных int4range не поддерживает включающую верхнюю границу при обычной записи.
--если нужно получить 10, как верхнюю границу, то нужно указывать диапазон [1,11)sql
-- расширение диапазона
select range_merge('[1,5]'::int4range, '[3,10]'::int4range); --[1,11)
-- разность диапазонов
select '[1,10]'::int4range - '[4,10]'::int4range; -- [1,4)
-- длина диапазона
select numrange(1.5, 10.2),
upper(numrange(1.5, 10.2)) - lower(numrange(1.5, 10.2)) diff;text
numrange |diff |
----------+--------+
[1.5,10.2)| 8.7|Операторы сравнения и содержания
sql
@> — содержит ли диапазон элемент или другой диапазон
select '[1,10]'::int4range @> 5; -- true
select '[1,10]'::int4range @> '[2,8]'::int4range; -- true
<@ — содержится ли элемент в диапазоне или диапазон в другом диапазоне
select 5 <@ '[1,10]'::int4range; -- true
select '[2,8]'::int4range <@ '[1,10]'::int4range; -- true
&& — пересекаются ли диапазоны
select '[1,5]'::int4range && '[3,7]'::int4range; -- true
<< — строго левее
select '[1,5]'::int4range << '[6,10]'::int4range; -- true
>> — строго правее
select '[6,10]'::int4range >> '[1,5]'::int4range; -- true
&< — не расширяется правее правого диапазона
select '[1,5]'::int4range &< '[5,10]'::int4range; -- true
&> — не расширяется левее левого диапазона
select '[5,10]'::int4range &> '[1,5]'::int4range; -- true
-|- — примыкает (смежный)
select '[1,5]'::int4range -|- '[5,10]'::int4range; -- trueСоздание пользовательских диапазонных типов
sql
-- создаем enum тип для приоритетов
create type priority_level as enum ('very low', 'low', 'medium', 'high', 'very high');
-- создаем диапазонный тип на основе enum
create type priority_range as range (
subtype = priority_level
);
-- используем в таблице
create table tasks (
id serial primary key,
name varchar(100),
allowed_priority priority_range
);
--внесем несколько строк
insert into tasks (name, allowed_priority) values
('обычная задача', '[very low,high]'),
('срочная задача', '[medium,very high]');
--получим задачи, которые включают значение high
select *
from tasks
where allowed_priority @> 'high'::priority_level;text
id|name |allowed_priority |
--+--------------+--------------------+
1|обычная задача|["very low",high] |
2|срочная задача|[medium,"very high"]|Конструкторы диапазонов
Помимо приведения строк (например, '[1,10]'::int4range), PostgreSQL предоставляет функции-конструкторы для создания диапазонных значений. Синтаксис:
sql
диапазонный_тип( нижняя_граница, верхняя_граница [, текст_формат] )Параметр текст_формат - строка, состоящая из одного или двух символов, определяющих включение/исключение границ: '[]' - обе границы включены '[)' - нижняя включена, верхняя исключена '(]' - нижняя исключена, верхняя включена '()' - обе границы исключены Если параметр формата опущен, по умолчанию подразумевается '[)' (полуоткрытый интервал) для всех типов. Важно: PostgreSQL внутри всегда хранит диапазоны в полуоткрытом виде [), поэтому '[]' нормализуется в '[)'.
Примеры для целочисленных диапазонов:
sql
-- без формата
select int4range(1, 10); -- [1,10)
-- с форматом
select int4range(1, 10, '[)'); -- [1,10)
select int4range(1, 10, '[]'); -- [1,11)
select int4range(1, 10, '()'); -- [2,10)
-- открытые границы
select int4range(NULL, 10); -- (,10)
select int4range(1, NULL); -- [1,)
--NULL означает бесконечность только в семантике диапазона, но не является значением "infinity"Примеры для дат
sql
select daterange('2024-01-01', '2024-12-31', '[]'); -- [2024-01-01,2025-01-01)
select daterange('2024-01-01', '2024-12-31', '[)'); -- [2024-01-01,2024-12-31)Примеры для числовых диапазонов
sql
select numrange(1.5, 10.2, '[]'); -- [1.5,10.2]
select numrange(1.5, 10.2, '[)'); -- [1.5,10.2)В некоторых случаях конструкторы:
- безопаснее строкового кастинга при динамическом формировании значений
- удобная работа с открытыми границами через NULL
Заключение
Диапазонные типы данных в PostgreSQL позволяют работать с интервалами значений, что дает возможность:
- эффективно хранить и обрабатывать временные периоды, числовые диапазоны
- обеспечивать целостность данных через ограничения исключения
- создавать специализированные типы для конкретных бизнес-потребностей
- для диапазонных типов чаще всего используются GiST или SP-GiST индексы.
Независимо от способа создания и реализации, PostgreSQL гарантирует единое поведение операций над диапазонами за счёт нормализации границ.
Освоение диапазонных типов открывает новые возможности для создания надежных и эффективных приложений, работающих с интервальными данными.
При дальнейшем углублении в SQL вы можете создавать собственные диапазонные типы с применением процедурных языков и моделировать диапазоны с прерываниями.