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;
SELECT (DATE '2024-03-01', DATE '2024-03-10')
OVERLAPS (DATE '2024-03-10', DATE '2024-03-20') AS does_overlap;
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:
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.
OVERLAPSes 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 devuelvetruecuando 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; -- falseEl 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.(inicio, fin).date,timestamp,timee incluso un par(instante, interval).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.idpara 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
OVERLAPSes 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_endEsta forma es imprescindible cuando
OVERLAPSresulta incomodo o no existe — por ejemplo enMySQL, 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';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 deOVERLAPS, 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
idfalla a nivel de base de datos.ClickHouseno tiene niOVERLAPSni tipos range, asi que ahi recurres a la condicion manuala_start < b_end AND b_start < a_end.En resumen: para una comprobacion puntual en
WHEREusaOVERLAPS; para portabilidad, la comparacion manual; y para indexar mas garantias de integridad solidas, los tipos range conEXCLUDE.