Verifique la superposición de rangos de fechas en MySQL

81

Esta tabla se utiliza para almacenar sesiones (eventos):

CREATE TABLE session (
  id int(11) NOT NULL AUTO_INCREMENT
, start_date date
, end_date date
);

INSERT INTO session
  (start_date, end_date)
VALUES
  ("2010-01-01", "2010-01-10")
, ("2010-01-20", "2010-01-30")
, ("2010-02-01", "2010-02-15")
;

No queremos tener conflictos entre rangos.
Digamos que necesitamos insertar una nueva sesión de 2010-01-05 a 2010-01-25 .
Nos gustaría conocer las sesiones en conflicto.

Aquí está mi consulta:

SELECT *
FROM session
WHERE "2010-01-05" BETWEEN start_date AND end_date
   OR "2010-01-25" BETWEEN start_date AND end_date
   OR "2010-01-05" >= start_date AND "2010-01-25" <= end_date
;

Aquí está el resultado:

+----+------------+------------+
| id | start_date | end_date   |
+----+------------+------------+
|  1 | 2010-01-01 | 2010-01-10 |
|  2 | 2010-01-20 | 2010-01-30 |
+----+------------+------------+

¿Existe una mejor manera de conseguirlo?


violín

Pierre de LESPINAY
fuente
1
Tu tercera condición es incorrecta. Se supone que debe ser "2010-01-05" <= start_date AND "2010-01-25" >= end_date. Consulte stackoverflow.com/a/28802972/632951 para ver la visualización. Su tercera condición actual nunca se evaluará, porque la primera (y la segunda) condición ya la cubre.
Pacerier

Respuestas:

155

Tuve una consulta de este tipo con una aplicación de calendario que escribí una vez. Creo que usé algo como esto:

... WHERE new_start < existing_end
      AND new_end   > existing_start;

ACTUALIZACIÓN Esto definitivamente debería funcionar ((ns, ne, es, ee) = (new_start, new_end, existing_start, existing_end)):

  1. ns - ne - es - ee: no se superpone y no coincide (porque ne <es)
  2. ns - es - ne - ee: superposiciones y coincidencias
  3. es - ns - ee - ne: superposiciones y coincidencias
  4. es - ee - ns - ne: no se superpone y no coincide (porque ns> ee)
  5. es - ns - ne - ee: superposiciones y coincidencias
  6. ns - es - ee - ne: superposiciones y coincidencias

Aquí hay un violín

fusión de almas
fuente
7
¡Funciona muy bien !, pero creo que @Pierre de LESPINAY está buscando rangos inclusivos en su consulta: WHERE new_start <= existing_end AND new_end> = existing_start;
Osvaldo Mercado
14
@OsvaldoM. Si realmente lo fuera, se habría quejado hace 2 años…
soulmerge
Si un evento termina hoy y otro comienza hoy, ¿se superponen? En mi opinión, se superpondrán. Pero la consulta SQL no lo dirá: sqlfiddle.com/#!2/0a6fd/1/0
AL
2
@soulmerge, en realidad, habría agregado el =en su código real en lugar de molestarse en quejarse.
Pacerier
32
SELECT * FROM tbl WHERE
existing_start BETWEEN $newStart AND $newEnd OR 
existing_end BETWEEN $newStart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

if (!empty($result))
throw new Exception('We have overlapping')

Estas 3 líneas de cláusulas sql cubren los 4 casos de superposición requeridos.

Yasen
fuente
6
Incluso si el OP aparentemente no estaba buscando esta definición superpuesta, esta respuesta es la mejor solución para el problema descrito por el nombre de la pregunta. Estaba buscando esta superposición, que es la verdadera superposición.
Cec
1
No estoy seguro de lo que quiere decir con "superposición verdadera", pero la respuesta principal de @soulmerge es equivalente a esta respuesta de Lamy. Pude usar Z3 Theorem Solver para demostrar la equivalencia: grantjenks.com/projects/equivalent-inequalities
GrantJ
16

La respuesta de Lamy es buena, pero puedes optimizarla un poco más.

SELECT * FROM tbl WHERE
existing_start BETWEEN $newSTart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

Esto detectará los cuatro escenarios donde los rangos se superponen y excluirá los dos donde no lo hacen.

LordJavac
fuente
¿Hay otras soluciones además de esta y las otras dos anteriores?
Pacerier
4

Me había enfrentado a un problema similar. Mi problema fue dejar de reservar entre un rango de fechas bloqueadas. Por ejemplo, la reserva está bloqueada para una propiedad entre el 2 de mayo y el 7 de mayo. Necesitaba encontrar cualquier tipo de fecha superpuesta para detectar y detener la reserva. Mi solución es similar a LordJavac.

SELECT * FROM ib_master_blocked_dates WHERE venue_id=$venue_id AND 
(
    (mbd_from_date BETWEEN '$from_date' AND '$to_date') 
    OR
    (mbd_to_date BETWEEN  '$from_date' AND '$to_date')
    OR
    ('$from_date' BETWEEN mbd_from_date AND mbd_to_date)
    OR      
    ('$to_date' BETWEEN mbd_from_date AND mbd_to_date)      
)
*mbd=master_blocked_dates

Avísame si no funciona.

Niraj Kumar
fuente
2

Dados dos intervalos como (s1, e1) y (s2, e2) con s1 <e1 y s2 <e2
Puede calcular la superposición de la siguiente manera:

SELECT 
     s1, e1, s2, e2,
     ABS(e1-s1) as len1,
     ABS(e2-s2) as len2,
     GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0)>0 as overlaps,
     GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0) as overlap_length
FROM test_intervals 

También funcionará si un intervalo está dentro del otro.

Mackraken
fuente
0

Recientemente, estaba luchando con el mismo problema y terminé con este sencillo paso (puede que este no sea un buen enfoque o que consuma memoria):

SELECT * FROM duty_register WHERE employee = '2' AND (
(
duty_start_date BETWEEN {$start_date} AND {$end_date}
OR
duty_end_date BETWEEN {$start_date} AND {$end_date}
)
OR
(
{$start_date} BETWEEN duty_start_date AND duty_end_date
OR
{$end_date} BETWEEN duty_start_date AND duty_end_date)
);

Esto me ayudó a encontrar las entradas con rangos de fechas superpuestos.

Espero que esto ayude a alguien.

Sahu V Kumar
fuente
0

Puede cubrir todos los casos de superposición de fechas incluso cuando hasta la fecha en la base de datos posiblemente pueda ser nulo de la siguiente manera:

SELECT * FROM `tableName` t
WHERE t.`startDate` <= $toDate
AND (t.`endDate` IS NULL OR t.`endDate` >= $startDate);

Esto devolverá todos los registros que se superponen con las nuevas fechas de inicio / finalización de todos modos.

Hussein Akar
fuente
0

La respuesta de Mackraken anterior es mejor desde una perspectiva de desempeño, ya que no requiere varios OR para evaluar si dos fechas se superponen. ¡Buena solución!

Sin embargo, encontré que en MySQL necesitas usar DATEDIFF en lugar del operador menos -

SELECT o.orderStart, o.orderEnd, s.startDate, s.endDate
, GREATEST(LEAST(orderEnd, endDate) - GREATEST(orderStart, startDate), 0)>0 as overlaps
, DATEDIFF(LEAST(orderEnd, endDate), GREATEST(orderStart, startDate)) as overlap_length
FROM orders o
JOIN dates s USING (customerId)
WHERE 1
AND DATEDIFF(LEAST(orderEnd, endDate),GREATEST(orderStart, startDate)) > 0;
yg-dba
fuente