Comparaison des plages de dates

116

Dans MySQL, si j'ai une liste de plages de dates (début de plage et fin de plage). par exemple

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

Et je veux vérifier si une autre plage de dates contient UNE des plages déjà dans la liste, comment puis-je faire cela?

par exemple

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
la source
1
duplication possible de Déterminer si deux plages de dates se chevauchent
Salman A

Réponses:

439

C'est un problème classique, et c'est en fait plus facile si vous inversez la logique.

Laisse moi te donner un exemple.

Je vais publier une période ici, et toutes les différentes variations d'autres périodes qui se chevauchent d'une manière ou d'une autre.

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

par contre, permettez-moi de poster tous ceux qui ne se chevauchent pas:

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

Donc, si vous réduisez simplement la comparaison à:

starts after end
ends before start

alors vous trouverez toutes celles qui ne se chevauchent pas, puis vous trouverez toutes les périodes non correspondantes.

Pour votre dernier exemple NOT IN LIST, vous pouvez voir qu'il correspond à ces deux règles.

Vous devrez décider si les périodes suivantes sont à l'intérieur ou à l'extérieur de vos plages:

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

Si votre table a des colonnes appelées range_end et range_start, voici un SQL simple pour récupérer toutes les lignes correspondantes:

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

Notez le PAS là-dedans. Puisque les deux règles simples trouvent toutes les lignes non correspondantes , un simple NOT l'inversera pour dire: si ce n'est pas l'une des lignes non correspondantes, elle doit être l'une des lignes correspondantes .

Appliquez ici une logique d'inversion simple pour vous débarrasser du NOT et vous vous retrouverez avec:

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start
Lasse V. Karlsen
la source
45
Nous avons besoin d'un indicateur "contient des diagrammes ACII" pour les réponses qui vous permettent de les voter plus d'une fois
Jonny Buchanan
29
Probablement l'une des 5 meilleures réponses que j'ai vues sur SO. Excellente explication du problème, belle présentation de la solution et ... photos!
davidavr le
10
Si je pouvais voter plus d'une fois, je le ferais. Explication géniale, claire et concise d'un problème commun qui se pose, une solution que j'ai rarement vue aussi bien expliquée!
ConroyP le
2
Très bonne réponse! La seule chose que j'ajouterais - en référence au fait de décider si les points de terminaison sont inclus ou non - tout fonctionne plus proprement si vous choisissez un intervalle fermé d'un côté et un intervalle ouvert de l'autre. Par exemple, le début d'une plage inclut dans le point et la fin de la plage ne l'est pas. Surtout lorsque vous avez affaire à une combinaison de dates et d'heures de différentes résolutions, tout devient plus simple.
Eclipse
1
Bonne réponse. Ceci est également décrit comme l'algèbre d'intervalle d'Allen . J'ai une réponse similaire et je me suis engagé dans une bataille féroce sur le nombre de comparaisons différentes avec un commentateur.
Jonathan Leffler
8

En prenant votre exemple de plage du 06/06/1983 au 18/06/1983 et en supposant que vous ayez des colonnes appelées début et fin pour vos plages, vous pouvez utiliser une clause comme celle-ci

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

c.-à-d. vérifiez que le début de votre plage de test est avant la fin de la plage de base de données, et que la fin de votre plage de test est après ou au début de la plage de base de données.

Paul Dixon
la source
4

Si votre SGBDR prend en charge la fonction OVERLAP (), cela devient trivial - pas besoin de solutions maison. (Dans Oracle, cela fonctionne apparemment mais n'est pas documenté).

David Aldridge
la source
1
Solution épique. Fonctionne très bien. Voici la syntaxe pour 2 plages de dates (s1, e1) et (s2, e2) dans Oracle: sélectionnez 1 dans dual où (s1, e1) se chevauche (s2, e2);
ihebiheb
0

Dans vos résultats attendus, vous dites

06/06/1983 au 18/06/1983 = EN LISTE

Cependant, cette période ne contient ni n'est contenue dans aucune des périodes de votre tableau (pas de liste!) De périodes. Il chevauche cependant la période du 10/06/1983 au 14/06/1983.

Vous pouvez trouver le livre Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) utile: il est antérieur à mysql mais le concept du temps n'a pas changé ;-)

un jour quand
la source
0

J'ai créé une fonction pour résoudre ce problème dans MySQL. Convertissez simplement les dates en secondes avant utilisation.

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
la source
0

Regardez dans l'exemple suivant. Cela vous sera 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
Rama Subba Reddy M
la source
0

Essayez ceci sur 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
la source
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
la source
0

Une autre méthode à l'aide de l'instruction BETWEEN SQL

Périodes incluses:

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

Périodes exclues:

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 - Conseil ATM
la source
-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
la source
Bienvenue dans Stack Overflow! Merci pour cet extrait de code, qui peut fournir une aide immédiate. Une explication appropriée améliorerait considérablement sa valeur éducative en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec des questions similaires, mais pas identiques. Veuillez modifier votre réponse pour ajouter une explication et donner une indication des limites et des hypothèses applicables.
Toby Speight