Comparación de rangos de fechas

116

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

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

Y quiero verificar si otro rango de fechas contiene CUALQUIERA de los rangos que ya están en la lista, ¿cómo haría 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
Kieran Benton
fuente
1
posible duplicado de Determinar si dos rangos de fechas se superponen
Salman A

Respuestas:

439

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

Dejame darte un ejemplo.

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 otro lado, permítanme publicar todos los que no se superponen:

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

Entonces, si simplemente reduce la comparación a:

starts after end
ends before start

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

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

Deberá decidir si los siguientes períodos están DENTRO o FUERA de sus rangos:

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

Si su tabla tiene columnas llamadas range_end y range_start, aquí hay un SQL simple para recuperar todas las filas coincidentes:

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

Tenga en cuenta el 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 que coinciden .

Aplicando una lógica de inversión simple aquí para deshacerse del NOT y terminará con:

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start
Lasse V. Karlsen
fuente
45
Necesitamos un indicador "contiene diagramas ACII" para las respuestas, lo que le permite votar a favor de ellos más de una vez
Jonny Buchanan
29
Probablemente una de las 5 mejores respuestas que he visto en SO. Gran explicación del problema, buen tutorial de la solución y ... ¡imágenes!
davidavr
10
Si pudiera votar esto más de una vez, lo haría. Excelente, clara y concisa explicación de un problema común que surge, ¡una solución que pocas veces he visto tan bien explicada!
ConroyP
2
¡Gran respuesta! Lo único que agregaría, en referencia a decidir si los puntos finales están incluidos o no, todo sale más limpio si va con un intervalo cerrado en un lado y un intervalo abierto en el otro. Por ejemplo, el inicio de un rango se incluye en el punto y el final del rango no. Especialmente cuando se trata de una combinación de fechas y horas de varias resoluciones, todo se vuelve más simple.
Eclipse
1
Buena respuesta. Esto también se describe como Álgebra de intervalos de Allen . Tengo una respuesta similar y me metí en una feroz batalla sobre cuántas comparaciones diferentes hay con un comentarista.
Jonathan Leffler
8

Tomando su rango de ejemplo del 06/06/1983 al 18/06/1983 y asumiendo que tiene columnas llamadas inicio y final para sus rangos, podría usar una cláusula como esta

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

es decir, compruebe que el inicio de su rango de prueba es antes del final del rango de la base de datos, y que el final de su rango de prueba es posterior o al comienzo del rango de la base de datos.

Paul Dixon
fuente
4

Si su RDBMS es compatible con la función OVERLAP (), entonces esto se vuelve trivial, sin necesidad de soluciones propias. (En Oracle aparentemente funciona pero no está documentado).

David Aldridge
fuente
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 tus resultados esperados dices

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

Sin embargo, este período no contiene ni está incluido en ninguno de los períodos en su tabla (¡no en la lista!) De períodos. Sin embargo, se superpone al período del 10/06/1983 al 14/06/1983.

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

un día cuando
fuente
0

Creé una función para lidiar con este problema en MySQL. Simplemente convierta las fechas en segundos antes de usarlas.

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 ;
Jonavon
fuente
0

Mire el siguiente ejemplo. Te será de gran ayuda.

    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 Userid=@userid) and  nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo
Rama Subba Reddy M
fuente
0

Prueba 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];
RickyS
fuente
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;
Paul Williamson
fuente
0

Otro método usando BETWEEN sql statement

Periodos incluidos:

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)
Florian HENRY - Consultoría de cajeros automáticos
fuente
-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>=@Fini AND a.dFechaFin <=@Ffin) OR
(a.dFechaIni>=@Fini AND a.dFechaFin >=@Ffin) )
Gio
fuente
¡Bienvenido a Stack Overflow! Gracias por este fragmento de código, que puede brindarle ayuda inmediata. Una explicación adecuada mejoraría enormemente su valor educativo al mostrar por qué es una buena solución al problema y lo haría más útil para futuros lectores con preguntas similares, pero no idénticas. Por favor, editar su respuesta para agregar explicación y dar una indicación de lo que se aplican limitaciones y supuestos.
Toby Speight