web-dev-qa-db-fra.com

Comparaison des plages de dates

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 l'une des plages déjà dans la liste, comment dois-je 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
110
Kieran Benton

Il s'agit d'un problème classique, et il est en fait plus facile si vous inversez la logique.

Laisse moi te donner un exemple.

Je posterai une période de temps ici, et toutes les différentes variations d'autres périodes qui se chevauchent d'une certaine manière.

           |-------------------|          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'autre part, 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

vous trouverez alors 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 contient des colonnes appelées range_end et range_start, voici quelques instructions SQL simples 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. Étant donné que 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 ceux qui correspondent.

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
409

En prenant votre exemple de plage du 06/06/1983 au 18/06/1983 et en supposant que vous avez des colonnes appelées start et end pour vos plages, vous pouvez 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 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.

8
Paul Dixon

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

4
David Aldridge

J'ai créé une fonction pour traiter 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 ;
0
jonavon

Regardez 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
0
Rama Subba Reddy M

Une autre méthode en utilisant 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)

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

Dans vos résultats attendus, vous dites

06/06/1983 au 18/06/1983 = DANS LA LISTE

Cependant, cette période ne contient ni n'est contenue par aucune des périodes de votre tableau (pas la 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 la notion de temps n'a pas changé ;-)

0
onedaywhen