Comparando intervalos de datas


116

No MySQL, se eu tiver uma lista de intervalos de datas (início e fim do intervalo). por exemplo

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

E eu quero verificar se outro intervalo de datas contém QUALQUER um dos intervalos já na lista, como eu faria isso?

por exemplo

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

Respostas:


439

Este é um problema clássico e, na verdade, é mais fácil se você inverter a lógica.

Deixe-me lhe dar um exemplo.

Vou postar um período de tempo aqui e todas as diferentes variações de outros períodos que se sobrepõem de alguma forma.

           |-------------------|          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 outro lado, deixe-me postar todos aqueles que não se sobrepõem:

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

Portanto, se você simplesmente reduzir a comparação para:

starts after end
ends before start

então você encontrará todos aqueles que não se sobrepõem e, em seguida, encontrará todos os períodos não correspondentes.

Para seu exemplo final NOT IN LIST, você pode ver que ele corresponde a essas duas regras.

Você precisará decidir se os seguintes períodos estão DENTRO ou FORA de seus intervalos:

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

Se sua tabela tem colunas chamadas range_end e range_start, aqui está um SQL simples para recuperar todas as linhas correspondentes:

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

Observe o NÃO aí. Visto que as duas regras simples encontram todas as linhas não correspondentes , um simples NÃO irá inverter para dizer: se não é uma das linhas não correspondentes, tem que ser uma das correspondentes .

Aplicando a lógica de reversão simples aqui para se livrar do NÃO e você acabará com:

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

45
Precisamos de um sinalizador "contém diagramas ACII" para as respostas, o que permite que você as vote mais de uma vez
Jonny Buchanan,

29
Provavelmente uma das 5 melhores respostas que já vi no SO. Ótima explicação do problema, bom passo a passo da solução e ... fotos!
davidavr

10
Se eu pudesse votar mais de uma vez, eu o faria. Explicação ótima, clara e concisa de um problema comum que surge, uma solução para a qual raramente vi tão bem explicada!
ConroyP

2
Ótima resposta! A única coisa que eu acrescentaria - em referência a decidir se os terminais são incluídos ou não - tudo funciona mais limpo se você escolher um intervalo fechado de um lado e um intervalo aberto do outro. Por exemplo, o início de um intervalo inclui o ponto e o final do intervalo não. Especialmente quando você está lidando com uma combinação de datas e horas de várias resoluções, tudo fica mais simples.
Eclipse

1
Boa resposta. Isso também é descrito como álgebra de intervalo de Allen . Eu tenho uma resposta semelhante e entrei em uma batalha feroz sobre quantas comparações diferentes existem com um comentarista.
Jonathan Leffler

8

Tomando o seu intervalo de exemplo de 06/06/1983 a 18/06/1983 e supondo que você tenha colunas chamadas início e fim para seus intervalos, você poderia usar uma cláusula como esta

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

ou seja, verifique se o início do seu intervalo de teste é antes do final do intervalo do banco de dados e se o final do seu intervalo de teste está após ou no início do intervalo do banco de dados.


4

Se o seu RDBMS suportar a função OVERLAP (), isso se torna trivial - não há necessidade de soluções caseiras. (No Oracle, aparentemente funciona, mas não é documentado).


1
Solução épica. Funciona bem. Esta é a sintaxe para 2 intervalos de datas (s1, e1) e (s2, e2) no Oracle: selecione 1 de dual onde (s1, e1) se sobrepõe (s2, e2);
ihebiheb

0

Em seus resultados esperados, você diz

06/06/1983 a 18/06/1983 = NA LISTA

No entanto, este período não contém nem está contido por nenhum dos períodos em sua tabela (não lista!) De períodos. No entanto, ele se sobrepõe ao período de 10/06/1983 a 14/06/1983.

Você pode achar o livro Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) útil: ele é anterior ao mysql, mas o conceito de tempo não mudou ;-)


0

Criei uma função para lidar com esse problema no MySQL. Basta converter as datas em 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

Veja o seguinte exemplo. Será útil para você.

    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

0

Experimente isto no 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
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

Outro método usando a instrução BETWEEN sql

Períodos incluídos:

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

Períodos excluídos:

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>=@Fini AND a.dFechaFin <=@Ffin) OR
(a.dFechaIni>=@Fini AND a.dFechaFin >=@Ffin) )

Bem-vindo ao Stack Overflow! Obrigado por este trecho de código, que pode fornecer alguma ajuda imediata. Uma explicação adequada melhoraria muito seu valor educacional, mostrando por que essa é uma boa solução para o problema, e a tornaria mais útil para futuros leitores com perguntas semelhantes, mas não idênticas. Por favor edite sua resposta para adicionar explicação, e dar uma indicação do que limitações e premissas se aplicam.
Toby Speight
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.