Пошук уроків, статей та іншого контенту
Навчитеся зберігати інтервали, перевіряти перетини й належність значень за допомогою range types.
Діапазонний тип (range type) зберігає не одне значення, а інтервал між нижньою та верхньою межами.
Наприклад:
період бронювання;
діапазон дат дії підписки;
інтервал числових значень;
проміжок часу, коли доступний ресурс.
PostgreSQL має вбудовані діапазонні типи:
int4range — діапазон integer;
int8range — діапазон bigint;
numrange — діапазон numeric;
tsrange — діапазон timestamp without time zone;
tstzrange — діапазон timestamp with time zone;
daterange — діапазон date.
Наприклад, тип daterange може містити період від 1 до 10 червня:
'[2026-06-01,2026-06-10)'Діапазон записується у форматі:
[нижня_межа, верхня_межа)Символи визначають, чи входить межа до діапазону:
[ — нижня межа включається;
( — нижня межа не включається;
] — верхня межа включається;
) — верхня межа не включається.
Приклади:
[1,10) -- від 1 включно до 10 не включно
(1,10] -- більше за 1 і до 10 включно
[1,10] -- від 1 до 10 включно
(1,10) -- строго між 1 і 10Для діапазонів дат часто використовують напіввідкритий запис [початок, кінець). Наприклад:
[2026-06-01,2026-06-10)означає всі дати від 1 червня включно до 10 червня не включно.
Такий формат зручний для послідовних інтервалів: [1, 10) і [10, 20) не перекриваються, але між ними немає пропущеного значення.
Діапазон можна передати як рядок із приведенням до потрібного типу:
SELECT '[1,10)'::int4range;
SELECT '[2026-06-01,2026-06-10)'::daterange;
SELECT '[10.5,20.75)'::numrange;Інший спосіб — використати функцію-конструктор:
SELECT int4range(1, 10, '[)');
SELECT daterange('2026-06-01', '2026-06-10', '[)');
SELECT tstzrange(
'2026-06-01 09:00:00+00',
'2026-06-01 18:00:00+00',
'[)'
);Третій аргумент конструктора задає межі. Якщо його не вказати, PostgreSQL використає [):
SELECT int4range(1, 10);Розглянемо таблицю бронювань кімнат. Для кожного запису зберігатимемо кімнату та часовий проміжок її бронювання.
CREATE TEMP TABLE reservations (
id integer GENERATED ALWAYS AS IDENTITY,
room_id integer NOT NULL,
period tstzrange NOT NULL,
CHECK (NOT isempty(period))
);
INSERT INTO reservations (room_id, period)
VALUES
(
101,
tstzrange(
'2026-06-10 14:00:00+00',
'2026-06-12 10:00:00+00',
'[)'
)
),
(
101,
tstzrange(
'2026-06-15 14:00:00+00',
'2026-06-17 10:00:00+00',
'[)'
)
),
(
102,
tstzrange(
'2026-06-11 09:00:00+00',
'2026-06-11 18:00:00+00',
'[)'
)
);
-- Пошук бронювань, які перетинаються із заданим періодом
SELECT id, room_id, period
FROM reservations
WHERE period && tstzrange(
'2026-06-11 12:00:00+00',
'2026-06-13 12:00:00+00',
'[)'
);
-- Пошук бронювань, що містять конкретний момент часу
SELECT id, room_id, period
FROM reservations
WHERE period @> '2026-06-11 15:00:00+00'::timestamptz;
-- Пошук бронювань конкретної кімнати
SELECT id, period
FROM reservations
WHERE room_id = 101
AND period && tstzrange(
'2026-06-16 08:00:00+00',
'2026-06-16 12:00:00+00',
'[)'
);Оператор && знаходить діапазони, які мають спільні значення. У першому запиті буде знайдено бронювання кімнати 101 з 10 до 12 червня та бронювання кімнати 102 11 червня.
Оператор @> у другому запиті перевіряє, чи містить діапазон конкретне значення.
SELECT daterange('[2026-01-01,2026-02-01)')
&& daterange('[2026-01-15,2026-03-01)');Результат — true, оскільки діапазони перетинаються.
SELECT daterange('[2026-01-01,2026-02-01)')
&& daterange('[2026-02-01,2026-03-01)');Результат — false. Перший діапазон не включає 1 лютого, а другий починається з 1 лютого.
Оператор @> перевіряє, чи містить діапазон значення:
SELECT daterange('[2026-06-01,2026-06-10)')
@> '2026-06-05'::date;Результат:
trueПеревірити можна і навпаки за допомогою <@:
SELECT '2026-06-05'::date
<@ daterange('[2026-06-01,2026-06-10)');Це еквівалентна перевірка: чи належить дата заданому діапазону.
@> також може перевіряти, чи містить один діапазон інший:
SELECT daterange('[2026-06-01,2026-07-01)')
@> daterange('[2026-06-10,2026-06-20)');Результат — true.
Зворотна форма:
SELECT daterange('[2026-06-10,2026-06-20)')
<@ daterange('[2026-06-01,2026-07-01)');Оператор -|- перевіряє, чи є діапазони суміжними, тобто чи стикаються вони, але не перетинаються:
SELECT daterange('[2026-06-01,2026-06-10)')
-|- daterange('[2026-06-10,2026-06-20)');Результат — true.
Для роботи з окремими частинами діапазону використовують функції:
lower(range) — нижня межа;
upper(range) — верхня межа;
lower_inc(range) — чи включена нижня межа;
upper_inc(range) — чи включена верхня межа;
lower_inf(range) — чи є нижня межа нескінченною;
upper_inf(range) — чи є верхня межа нескінченною;
isempty(range) — чи є діапазон порожнім.
SELECT
lower(period) AS starts_at,
upper(period) AS ends_at,
lower_inc(period) AS starts_included,
upper_inc(period) AS ends_included,
isempty(period) AS is_empty
FROM reservations;Для діапазону [) результатом буде:
нижня межа включена;
верхня межа не включена.
Діапазон може не мати однієї або обох меж:
SELECT '(,10)'::int4range;
SELECT '[10,)'::int4range;
SELECT '(,)'::int4range;Такі діапазони означають відповідно:
усі значення, менші за 10;
усі значення, більші або рівні 10;
усі можливі значення.
У конструкторі NULL позначає необмежену межу:
SELECT int4range(NULL, 10, '()');
SELECT int4range(10, NULL, '[)');Під час перевірок PostgreSQL поводиться з необмеженою межею як із нескінченністю.
Порожній діапазон не містить жодного значення:
SELECT 'empty'::int4range;Його можна створити і через межі, які не утворюють жодного значення:
SELECT int4range(10, 10, '[)');Перевірка:
SELECT isempty('empty'::int4range);Результат — true.
Порожній діапазон відрізняється від NULL:
NULL означає відсутнє або невідоме значення;
порожній діапазон є відомим значенням, яке не містить елементів.
Якщо порожні діапазони не мають сенсу для предметної області, їх варто заборонити обмеженням:
CHECK (NOT isempty(period))int4range, int8range і daterange є дискретними діапазонами. Для них PostgreSQL має канонічне представлення — нижня межа включена, верхня не включена.
Наприклад, ці два діапазони для дат еквівалентні:
SELECT daterange('[2026-06-01,2026-06-10]');
SELECT daterange('[2026-06-01,2026-06-11)');Для daterange PostgreSQL нормалізує перший варіант до другого, оскільки наступний день після 10 червня — 11 червня.
Для неперервних значень, таких як timestamp або numeric, автоматичного переходу до іншого сусіднього значення немає. Тому в tsrange потрібно явно вибирати правильні межі.
Якщо таблиця містить багато діапазонів, для пошуку перетинів можна створити індекс GiST:
CREATE INDEX reservations_period_gist_idx
ON reservations
USING gist (period);Такий індекс особливо корисний для запитів з операторами:
&&;
@>;
<@;
-|-.
Сам індекс не змінює результат запиту, але може суттєво зменшити кількість рядків, які PostgreSQL перевіряє.
Для діапазону [2026-06-01,2026-06-10) дата 10 червня вже не входить до діапазону. Якщо період має включати весь день 10 червня, для daterange зазвичай вказують верхню межу 11 червня:
SELECT daterange('[2026-06-01,2026-06-11)');NULL і порожнім діапазономNULL не означає «порожній діапазон». Це відсутнє значення. Для явного порожнього діапазону потрібно використовувати:
'empty'::int4range=Перевірка рівності діапазонів не замінює перевірку перетину. Якщо потрібно дізнатися, чи два періоди перетинаються, слід використовувати &&:
WHERE period && tstzrange(...)tsrange зберігає timestamp without time zone, а tstzrange — timestamp with time zone. Для подій, що відбуваються в реальному часі та можуть приходити з різних часових поясів, потрібно свідомо вибрати відповідний тип. Не слід змішувати ці типи без явного приведення.
Діапазонні типи зберігають інтервали як одне значення.
Для дат використовується daterange, для часових міток — tsrange або tstzrange.
Запис [) означає включену нижню та невключену верхню межу.
&& перевіряє перетин діапазонів.
@> перевіряє, чи містить діапазон значення або інший діапазон.
<@ перевіряє належність значення або діапазону іншому діапазону.
-|- перевіряє суміжність діапазонів.
lower() і upper() повертають межі, а isempty() перевіряє порожній діапазон.
Для ефективного пошуку перетинів використовують індекс GiST.
Для послідовних періодів найчастіше зручно використовувати напіввідкриті діапазони [початок, кінець).