Confronto di intervalli di date


116

In MySQL, se ho un elenco di intervalli di date (inizio intervallo e fine intervallo). per esempio

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

E voglio controllare se un altro intervallo di date contiene QUALSIASI degli intervalli già nell'elenco, come dovrei farlo?

per esempio

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

Risposte:


439

Questo è un problema classico e in realtà è più facile se si inverte la logica.

Lasciate che vi faccia un esempio.

Pubblicherò qui un periodo di tempo e tutte le diverse varianti di altri periodi che si sovrappongono in qualche modo.

           |-------------------|          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

d'altra parte, lasciami pubblicare tutti quelli che non si sovrappongono:

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

Quindi, se riduci semplicemente il confronto a:

starts after end
ends before start

poi troverai tutti quelli che non si sovrappongono, e poi troverai tutti i periodi non corrispondenti.

Per il tuo esempio finale NOT IN LIST, puoi vedere che corrisponde a queste due regole.

Dovrai decidere se i seguenti periodi sono IN o FUORI dai tuoi intervalli:

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

Se la tua tabella ha colonne chiamate range_end e range_start, ecco un semplice SQL per recuperare tutte le righe corrispondenti:

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

Nota il NON lì dentro. Poiché le due semplici regole trova tutte le righe non corrispondenti , un semplice NOT lo invertirà per dire: se non è una delle righe non corrispondenti, deve essere una di quelle corrispondenti .

Applicando qui una semplice logica di inversione per sbarazzarsi del NOT e si finisce con:

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

45
Abbiamo bisogno di un flag "contiene diagrammi ACII" per le risposte che ti consenta di votare più di una volta
Jonny Buchanan

29
Probabilmente una delle 5 migliori risposte che ho visto su SO. Ottima spiegazione del problema, bella panoramica della soluzione e ... immagini!
davidavr

10
Se potessi votare più di una volta, lo farei. Spiegazione grande, chiara e concisa di un problema comune che si presenta, una soluzione che raramente ho visto così ben spiegata!
ConroyP

2
Bella risposta! L'unica cosa che aggiungerei - in riferimento alla decisione se gli endpoint sono inclusi o meno - tutto funziona in modo più pulito se vai con un intervallo chiuso su un lato e un intervallo aperto sull'altro. Ad esempio, l'inizio di un intervallo include nel punto e la fine dell'intervallo no. Soprattutto quando hai a che fare con una combinazione di date e orari di varie risoluzioni, tutto diventa più semplice.
Eclipse

1
Buona risposta. Questo è anche descritto come Algebra degli intervalli di Allen . Ho una risposta simile e sono entrato in una feroce battaglia su quanti diversi confronti ci sono con un commentatore.
Jonathan Leffler

8

Prendendo il tuo intervallo di esempio dal 06/06/1983 al 18/06/1983 e supponendo che tu abbia colonne chiamate inizio e fine per i tuoi intervalli, potresti usare una clausola come questa

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

vale a dire verificare che l'inizio dell'intervallo di test sia prima della fine dell'intervallo di database e che la fine dell'intervallo di test sia dopo o all'inizio dell'intervallo di database.


4

Se il tuo RDBMS supporta la funzione OVERLAP (), questo diventa banale: non sono necessarie soluzioni interne. (In Oracle apparentemente funziona ma non è documentato).


1
Soluzione epica. Funziona bene. Questa è la sintassi per 2 intervalli di date (s1, e1) e (s2, e2) in Oracle: selezionare 1 da dual dove (s1, e1) si sovrappone (s2, e2);
ihebiheb

0

Nei tuoi risultati attesi dici

Dal 06/06/1983 al 18/06/1983 = IN LISTA

Tuttavia, questo periodo non contiene né è contenuto da nessuno dei periodi nella tabella (non nell'elenco!) Di periodi. Tuttavia, si sovrappone al periodo dal 10/06/1983 al 14/06/1983.

Potresti trovare utile il libro Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ): è precedente a mysql ma il concetto di tempo non è cambiato ;-)


0

Ho creato una funzione per affrontare questo problema in MySQL. Basta convertire le date in secondi prima dell'uso.

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

Guarda il seguente esempio. Ti sarà utile.

    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

Prova questo su 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

Un altro metodo utilizzando BETWEEN sql statement

Periodi inclusi:

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

Periodi esclusi:

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) )

Benvenuto in Stack Overflow! Grazie per questo snippet di codice, che potrebbe fornire un aiuto immediato. Una spiegazione adeguata migliorerebbe notevolmente il suo valore educativo mostrando perché questa è una buona soluzione al problema e la renderebbe più utile ai futuri lettori con domande simili, ma non identiche. Si prega di modificare la risposta di aggiungere una spiegazione, e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni.
Toby Speight
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.