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