120 votes

Comparaison des plages de dates

Dans MySQL, si je dispose d'une liste de plages de dates (début 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 TOUTES les plages déjà présentes dans la liste, comment faire ?

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

1 votes

453voto

Lasse V. Karlsen Points 148037

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

Laissez-moi vous donner un exemple.

Je vais afficher une période de temps 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

d'un autre côté, laissez-moi afficher 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

puis vous trouverez toutes celles qui ne se chevauchent pas, et enfin vous trouverez toutes les périodes non concordantes.

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 DANS ou HORS de vos plages :

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

Si votre table possède des colonnes appelées range_end et range_start, voici un langage 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 non assortie les rangs, un simple NOT l'inversera pour dire : si ce n'est pas une des rangées non correspondantes, ce doit être une des rangées correspondantes. .

En appliquant une simple logique d'inversion ici pour se débarrasser du NOT, on obtient.. :

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

47 votes

Nous avons besoin d'un drapeau "contient des diagrammes ACII" pour les réponses qui vous permet de les upvoter plus d'une fois.

29 votes

Probablement l'une des 5 meilleures réponses que j'ai vues sur SO. Excellente explication du problème, bonne explication de la solution, et... des photos !

0 votes

Oui, c'est absolument génial, merci, j'avais l'impression de regarder dans le mauvais sens et de me débattre avec les dates dans ma tête. Je viens d'écrire mes requêtes en utilisant l'inverse et c'est très clair maintenant !

8voto

Paul Dixon Points 122033

En prenant l'exemple de la plage du 06/06/1983 au 18/06/1983 et en supposant que vous avez des colonnes appelées commencer y fin pour vos plages, vous pourriez utiliser une clause comme celle-ci

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

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

5voto

David Aldridge Points 27624

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

1 votes

Solution épique. Elle fonctionne bien. Voici la syntaxe pour 2 plages de dates (s1,e1) et (s2,e2) dans Oracle : select 1 from dual where (s1,e1) overlaps (s2,e2) ;

0voto

onedaywhen Points 24594

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 par aucune des périodes de votre tableau (pas liste !) de périodes. Elle chevauche cependant la période du 10/06/1983 au 14/06/1983.

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

0voto

jonavon Points 138

J'ai créé une fonction pour traiter ce problème dans MySQL. Il suffit de convertir les dates en secondes avant de les utiliser.

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 ;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X