2008-09-27 15 views
105

En MySQL, si tengo una lista de rangos de fechas (rango-inicio y rango-fin). p.ej.Comparación de rangos de fechas

10/06/1983 to 14/06/1983 
15/07/1983 to 16/07/1983 
18/07/1983 to 18/07/1983 

Y quiero comprobar si otro intervalo de tiempo contiene cualquiera de los rangos que ya están en la lista, ¿cómo iba a hacer eso?

p. Ej.

06/06/1983 to 18/06/1983 = IN LIST 
10/06/1983 to 11/06/1983 = IN LIST 
14/07/1983 to 14/07/1983 = NOT IN LIST 
+1

posible duplicado de [determinar si dos intervalos de fecha de superposición] (http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap) –

Respuesta

384

Este es un problema clásico, y en realidad es más fácil si se invierte la lógica.

Déjeme darle un ejemplo.

Voy a publicar un período de tiempo aquí, y todas las diferentes variaciones de otros períodos que se superponen de alguna manera.

  |-------------------|   compare to this one 
       |---------|    contained within 
      |----------|     contained within, equal start 
        |-----------|   contained within, equal end 
      |-------------------|   contained within, equal start+end 
    |------------|      not fully contained, overlaps start 
        |---------------|  not fully contained, overlaps end 
    |-------------------------|   overlaps start, bigger 
      |-----------------------|  overlaps end, bigger 
    |------------------------------|  overlaps entire period 

por el contrario, me deja publicar todos aquellos que no se solapa:

  |-------------------|   compare to this one 
    |---|        ends before 
           |---| starts after 

lo tanto, si la comparación sencilla reduce a:

starts after end 
ends before start 

entonces te encuentre todos aquellos que no se superponen, y luego encontrará todos los períodos que no coinciden.

Para su ejemplo final NOT IN LIST, puede ver que coincide con esas dos reglas.

Usted tendrá que decidir wether los siguientes períodos están en o fuera de sus rangos:

  |-------------| 
    |-------|      equal end with start of comparison period 
         |-----| equal start with end of comparison period 

Si la tabla tiene columnas llamadas RANGE_END y RANGE_START, aquí hay algo de SQL sencilla para recuperar todas las filas coincidentes:

SELECT * 
FROM periods 
WHERE NOT (range_start > @check_period_end 
      OR range_end < @check_period_start) 

Nota del nO allí. Dado que las dos reglas simples encuentran todas las filas que no coinciden, un NOT simple lo invertirá para decir: si no es una de las filas que no coinciden, tiene que ser una de las equivalentes.

aplicación lógica de inversión simple aquí para deshacerse del NO y que va a terminar con:

SELECT * 
FROM periods 
WHERE range_start <= @check_period_end 
     AND range_end >= @check_period_start 
+42

Necesitamos una bandera que contenga "diagramas ACII" para las respuestas que le permita votarlas más de una vez. –

+25

Probablemente sea una de las 5 mejores respuestas que he visto en SO. Gran explicación del problema, buen recorrido de la solución y ... ¡imágenes! – davidavr

+0

Sí, eso fue absolutamente genial gracias, absolutamente lo estaba mirando al revés y luchando con las fechas en mi cabeza. ¡Acabo de escribir mis consultas usando la inversa y ahora está muy claro! –

8

Tomando su gama ejemplo de 06/06/1983 a 18/06/1983 y asumiendo tiene columnas llamadas comenzar y final para sus rangos, se puede utilizar una cláusula como esta

where ('1983-06-06' <= end) and ('1983-06-18' >= start) 

es decir, comprobar el inicio de yo El rango de prueba se encuentra antes del final del rango de la base de datos, y el final de su rango de prueba está después o al comienzo del rango de la base de datos.

4

Si su RDBMS es compatible con la función OVERLAP(), esto se convierte en algo trivial: no hay necesidad de soluciones locales.(En Oracle, aparentemente funciona pero no está documentado).

+1

Solución épica. Funciona bien. Esta es la sintaxis para 2 rangos de fechas (s1, e1) y (s2, e2) en Oracle: seleccione 1 de dual donde (s1, e1) se superpone (s2, e2); – ihebiheb

0

En los resultados esperados que dicen

06/06/1983 al 18/06/1983 = EN LISTA

Sin embargo, este período no contiene ni está contenido por cualquiera de los periodos de la tabla (¡no la lista!) de los períodos. Sin embargo, se superpone el período del 10/06/1983 al 14/06/1983.

Usted puede encontrar el libro Snodgrass (http://www.cs.arizona.edu/people/rts/tdbbook.pdf) útil: es anterior a MySQL pero el concepto del tiempo no ha cambiado ;-)

0

creé función para hacer frente a este problema en MySQL. Simplemente convierta las fechas en segundos antes de usar.

DELIMITER ;; 

CREATE FUNCTION overlap_interval(x INT,y INT,a INT,b INT) 
RETURNS INTEGER DETERMINISTIC 
BEGIN 
DECLARE 
    overlap_amount INTEGER; 
    IF (((x <= a) AND (a < y)) OR ((x < b) AND (b <= y)) OR (a < x AND y < b)) THEN 
     IF (x < a) THEN 
      IF (y < b) THEN 
       SET overlap_amount = y - a; 
      ELSE 
       SET overlap_amount = b - a; 
      END IF; 
     ELSE 
      IF (y < b) THEN 
       SET overlap_amount = y - x; 
      ELSE 
       SET overlap_amount = b - x; 
      END IF; 
     END IF; 
    ELSE 
     SET overlap_amount = 0; 
    END IF; 
    RETURN overlap_amount; 
END ;; 

DELIMITER ; 
0

Mire el siguiente ejemplo. Te será útil.

SELECT DISTINCT RelatedTo,CAST(NotificationContent as nvarchar(max)) as NotificationContent, 
       ID, 
       Url, 
       NotificationPrefix, 
       NotificationDate 
       FROM NotificationMaster as nfm 
       inner join NotificationSettingsSubscriptionLog as nfl on nfm.NotificationDate between nfl.LastSubscribedDate and isnull(nfl.LastUnSubscribedDate,GETDATE()) 
    where ID not in(SELECT NotificationID from removednotificationsmaster where [email protected]) and nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo 
0
CREATE FUNCTION overlap_date(s DATE, e DATE, a DATE, b DATE) 
RETURNS BOOLEAN DETERMINISTIC 
RETURN s BETWEEN a AND b or e BETWEEN a and b or a BETWEEN s and e; 
0

intenta esto en MS SQL


WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, [ending date]) - DATEDIFF(DAY, [start date], [ending date]), 0) 
UNION ALL SELECT DATEADD(DAY, 1, calc_date) 
FROM date_range 
WHERE DATEADD(DAY, 1, calc_date) <= [ending date]) 
SELECT P.[fieldstartdate], P.[fieldenddate] 
FROM date_range R JOIN [yourBaseTable] P on Convert(date, R.calc_date) BETWEEN convert(date, P.[fieldstartdate]) and convert(date, P.[fieldenddate]) 
GROUP BY P.[fieldstartdate], P.[fieldenddate]; 
0

Otro método mediante el uso ENTRE instrucción SQL

períodos incluyen:

SELECT * 
FROM periods 
WHERE @check_period_start BETWEEN range_start AND range_end 
    AND @check_period_end BETWEEN range_start AND range_end 

Periodos excluidos:

SELECT * 
FROM periods 
WHERE (@check_period_start NOT BETWEEN range_start AND range_end 
    OR @check_period_end NOT BETWEEN range_start AND range_end) 
-2
SELECT * 
FROM tabla a 
WHERE (@Fini <= a.dFechaFin AND @Ffin >= a.dFechaIni) 
    AND ((@Fini >= a.dFechaIni AND @Ffin <= a.dFechaFin) OR (@Fini >= a.dFechaIni AND @Ffin >= a.dFechaFin) OR (a.dFechaIni>[email protected] AND a.dFechaFin <[email protected]) OR 
(a.dFechaIni>[email protected] AND a.dFechaFin >[email protected])) 
+0

¡Bienvenido a Stack Overflow! Gracias por este fragmento de código, que puede brindar alguna ayuda inmediata. Una explicación adecuada [mejoraría en gran medida] (// meta.stackexchange.com/q/114762) su valor educativo al mostrar * por qué * esta es una buena solución al problema, y ​​lo haría más útil para lectores futuros con similares, pero no idénticas, preguntas. Por favor [edite] su respuesta para agregar una explicación y dar una indicación de qué limitaciones y suposiciones se aplican. –

Cuestiones relacionadas