sqlpostgresqloverlapsranges

El operador OVERLAPS en SQL: cruce de periodos y deteccion de conflictos

Como usar OVERLAPS para comprobar si dos periodos de tiempo se cruzan y detectar conflictos de reservas y turnos en PostgreSQL.

2 min de lecturaReferencesql · postgresql · overlaps · ranges · dates

OVERLAPS es un operador infijo del estandar SQL que responde a una sola pregunta: se cruzan dos periodos de tiempo? Te ahorra comparar cuatro fechas a mano y es ideal para detectar conflictos en reservas, turnos y suscripciones.

Sintaxis basica

Cada periodo es un par entre parentesis: (start, end). La expresion devuelve true cuando los intervalos comparten algun instante.

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

El segundo ejemplo devuelve false, y eso es lo clave que debes interiorizar: los limites se tratan como un intervalo semiabierto [start, end). Que el fin de un periodo toque el inicio de otro no cuenta como cruce, justo lo que quieres para turnos consecutivos.

  • Cada operando es un par (inicio, fin).
  • Acepta date, timestamp, time e incluso un par (instante, interval).
  • Si el inicio es mayor que el fin, PostgreSQL los intercambia en silencio.

Detectar conflictos de reservas

Supongamos que las reservas se asocian a pedidos. Queremos cualquier pedido cuyo periodo se cruce con una nueva peticion del 5 al 8 de marzo:

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');

Para encontrar cada par de periodos solapados dentro de una misma tabla, hacemos un autojoin y usamos o1.id < o2.id para descartar duplicados y la comparacion de una fila consigo misma:

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');

El equivalente con comparacion de fechas

OVERLAPS es azucar sintactico sobre la condicion clasica. Dos periodos se cruzan si y solo si:

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

Esta forma es imprescindible cuando OVERLAPS resulta incomodo o no existe — por ejemplo en MySQL, que carece de este operador. La misma busqueda de conflictos:

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';

Cuidado: un error frecuente es escribir <= en vez de <. Con < estricto, los periodos adyacentes (fin = inicio) no se consideran conflicto, igual que OVERLAPS. Si de verdad quieres que el contacto cuente como cruce (por ejemplo, para cobertura continua), usa <= — pero entonces el comportamiento difiere de OVERLAPS.

Los tipos range como alternativa

PostgreSQL tiene tipos de rango dedicados (tsrange, daterange, int4range) y un operador de solape &&. Se lee mejor y, a diferencia de OVERLAPS, se puede indexar con 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');

La gran ventaja son las restricciones de exclusion: la propia base de datos garantiza que los periodos nunca se crucen, sin condiciones de carrera a nivel de aplicacion. Por ejemplo, puedes prohibir reservar a un empleado para dos turnos a la vez:

ALTER TABLE employees
  ADD COLUMN shift tsrange;

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

Cualquier intento de insertar un turno solapado para el mismo id falla a nivel de base de datos. ClickHouse no tiene ni OVERLAPS ni tipos range, asi que ahi recurres a la condicion manual a_start < b_end AND b_start < a_end.

En resumen: para una comprobacion puntual en WHERE usa OVERLAPS; para portabilidad, la comparacion manual; y para indexar mas garantias de integridad solidas, los tipos range con EXCLUDE.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador