sqlpostgresqloverlapsranges

The SQL OVERLAPS Operator: Detecting Period Intersections and Conflicts

How to use OVERLAPS to test whether two time periods intersect and to find booking and shift conflicts in PostgreSQL.

2 perc olvasásReferencesql · postgresql · overlaps · ranges · dates
Ez a cikk jelenleg oroszul van — az angol fordítás folyamatban van.

OVERLAPS — это инфиксный оператор стандарта SQL, который отвечает на один вопрос: пересекаются ли два периода времени? Он избавляет от ручного сравнения четырёх дат и идеально подходит для поиска накладок в бронированиях, сменах и подписках.

Базовый синтаксис

Каждый период задаётся парой в скобках: (start, end). Выражение возвращает true, если интервалы имеют общую точку.

SELECT (DATE '2024-03-01', DATE '2024-03-10')
    OVERLAPS (DATE '2024-03-08', DATE '2024-03-15')  AS does_overlap;  -- true

SELECT (DATE '2024-03-01', DATE '2024-03-10')
    OVERLAPS (DATE '2024-03-10', DATE '2024-03-20')  AS does_overlap;  -- false

Второй пример возвращает false, и это главное, что нужно усвоить: границы трактуются как полуоткрытый интервал [start, end). Конец одного периода, совпадающий с началом другого, накладкой не считается — что удобно для встык идущих смен: смена с 09:00 до 17:00 не конфликтует со сменой с 17:00 до 01:00. Если бы граница была включающей, такие соседние смены ложно помечались бы как пересекающиеся.

  • Каждый операнд — это пара (start, end), то есть начало и конец.
  • Поддерживаются date, timestamp, time, а также пара «момент плюс interval».
  • Если начало больше конца, PostgreSQL молча меняет их местами.

Поиск конфликтов в бронированиях

Представим, что у нас есть резервации, привязанные к заказам. Нужно найти заказ, чей период пересекается с новым запросом на 5–8 марта:

SELECT o.id, o.user_id
FROM orders o
WHERE (o.created_at, o.created_at + INTERVAL '3 days')
   OVERLAPS (TIMESTAMP '2024-03-05', TIMESTAMP '2024-03-08');

Чтобы найти все пары пересекающихся периодов внутри одной таблицы, делаем самосоединение и условием o1.id < o2.id отсекаем дубли и сравнение строки с самой собой:

SELECT o1.id AS first_id, o2.id AS second_id
FROM orders o1
JOIN orders o2
  ON o1.user_id = o2.user_id
 AND o1.id < o2.id
 AND (o1.created_at, o1.created_at + INTERVAL '1 hour')
  OVERLAPS (o2.created_at, o2.created_at + INTERVAL '1 hour');

Эквивалент через сравнение дат

OVERLAPS — это синтаксический сахар над классическим условием. Два периода пересекаются тогда и только тогда, когда выполнено:

-- a_start < b_end AND b_start < a_end
WHERE a_start < b_end
  AND b_start < a_end

Эта форма незаменима, когда OVERLAPS неудобен или недоступен — например, в MySQL, где оператора нет вовсе. Тот же поиск накладок:

SELECT o1.id, o2.id
FROM orders o1
JOIN orders o2
  ON o1.user_id = o2.user_id
 AND o1.id < o2.id
 AND o1.created_at < o2.created_at + INTERVAL '1 hour'
 AND o2.created_at < o1.created_at + INTERVAL '1 hour';

Ловушка: распространённая ошибка — писать <= вместо <. Со строгим < смежные периоды (конец = начало) не считаются конфликтом, ровно как у OVERLAPS. Если же вам нужно считать касание накладкой (например, для непрерывного покрытия), берите <= — но тогда поведение разойдётся с OVERLAPS.

Range-типы как альтернатива

В PostgreSQL есть отдельные диапазонные типы (tsrange, daterange, int4range) и оператор пересечения &&. Это читабельнее и, в отличие от OVERLAPS, индексируется через GiST.

SELECT o.id
FROM orders o
WHERE tsrange(o.created_at, o.created_at + INTERVAL '3 days')
   && tsrange(TIMESTAMP '2024-03-05', TIMESTAMP '2024-03-08');

Главное преимущество — constraint исключения: база сама гарантирует, что периоды не пересекутся, без гонок на уровне приложения. Например, нельзя забронировать одного сотрудника на две смены сразу. Важная деталь: ограничение ниже сравнивает id оператором =, а штатный GiST-класс операторов знает только && для диапазонов. Чтобы включить в один GiST-индекс проверку равенства по скалярной колонке, нужно расширение btree_gist. Без него EXCLUDE USING gist (id WITH =, ...) упадёт с ошибкой «data type integer has no default operator class for access method gist». Поэтому сперва выполняем CREATE EXTENSION btree_gist;, и только затем создаём ограничение:

ALTER TABLE employees
  ADD COLUMN shift tsrange;

ALTER TABLE employees
  ADD CONSTRAINT no_shift_conflict
  EXCLUDE USING gist (id WITH =, shift WITH &&);

Любая попытка вставить пересекающуюся смену для того же id упадёт с ошибкой на уровне БД. В ClickHouse ни OVERLAPS, ни range-типов нет — там используют ручное условие a_start < b_end AND b_start < a_end.

Итог: для разовой проверки в WHERE берите OVERLAPS, для переносимости — ручное сравнение, а для индексов и жёстких гарантий целостности — range-типы с EXCLUDE. Все три подхода описывают одно и то же пересечение полуоткрытых интервалов, поэтому переход между ними не меняет результат — меняется лишь читаемость, производительность и место, где живёт проверка.

Gyakorolj valós feladatokon

Oldj meg feladatokat az SQL-trénerben azonnali értékeléssel és tippekkel.

Tréner megnyitása