126

In MySQL, If I have a list of date ranges (range-start and range-end). e.g.

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

And I want to check if another date range contains ANY of the ranges already in the list, how would I do that?

e.g.

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
fthiella
  • 48,073
  • 15
  • 90
  • 106
Kieran Benton
  • 8,739
  • 12
  • 53
  • 77
  • 1
    possible duplicate of [Determine Whether Two Date Ranges Overlap](http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap) – Salman A Sep 03 '15 at 17:49
  • overlap? Easy - compare 1st start date against 2nd END date AND 1st end date against 2nd START date. – Fandango68 Jul 27 '23 at 23:01

10 Answers10

477

This is a classical problem, and it's actually easier if you reverse the logic.

Let me give you an example.

I'll post one period of time here, and all the different variations of other periods that overlap in some way.

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

on the other hand, let me post all those that doesn't overlap:

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

So if you simple reduce the comparison to:

starts after end
ends before start

then you'll find all those that doesn't overlap, and then you'll find all the non-matching periods.

For your final NOT IN LIST example, you can see that it matches those two rules.

You will need to decide wether the following periods are IN or OUTSIDE your ranges:

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

If your table has columns called range_end and range_start, here's some simple SQL to retrieve all the matching rows:

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

Note the NOT in there. Since the two simple rules finds all the non-matching rows, a simple NOT will reverse it to say: if it's not one of the non-matching rows, it has to be one of the matching ones.

Applying simple reversal logic here to get rid of the NOT and you'll end up with:

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start
Kijewski
  • 25,517
  • 12
  • 101
  • 143
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • 48
    We need a "contains ACII diagrams" flag for answers which lets you upvote them more than once – Jonny Buchanan Sep 27 '08 at 13:17
  • 29
    Probably one of the 5 best answers I've seen on SO. Great explanation of the problem, nice walkthrough of the solution, and ... pictures! – davidavr Sep 27 '08 at 13:21
  • Yeah that was absolutely brilliant thanks, absolutely was looking at it the wrong way round and struggling with dates in my head. Just written up my queries using the inverse and it is very clear now! – Kieran Benton Sep 27 '08 at 13:39
  • Super answer! Absolutely love the way it has been clearly explained. – Learning Sep 27 '08 at 15:09
  • 10
    If I could vote this up more than once, I would. Great, clear and concise explanation of a common issue that comes up, a solution to which I have rarely seen so well-explained! – ConroyP Sep 27 '08 at 16:47
  • 2
    Great answer! The only thing I'd add - in reference to deciding whether endpoints are included or not - everything works out cleaner if you go with a closed interval on one side and an open interval on the other. E.g. the start of a range includes in the point, and the end of the range doesn't. Especially when you're dealing with a combination of dates, and times of various resolutions, everything gets simpler. – Eclipse Nov 30 '10 at 19:21
  • 2
    Good answer. This is also described as [Allen's Interval Algebra](http://en.wikipedia.org/wiki/Allen%27s_Interval_Algebra). I have a similar [answer](http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap/328558#328558) and got into a fierce battle over how many different comparisons there are with one commentator. – Jonathan Leffler Jan 19 '11 at 16:01
  • I was breaking my brain over a similar problem and i was amazed how i did not see this approach. Thanks very much for this brilliant point of view, and the ASCII art – Metalmini Sep 27 '12 at 12:40
  • Only thing I'd add is that for this to work you need to validate your date ranges so that the end is never before the start, which isn't an issue for the much more complex not reversed method – MikeT Oct 07 '13 at 10:40
  • Thanks for answer. Assume we are using booking system. How to modify SQL if we want that customer will find `free dates` in booking system? Specially, interesting thing is how to find abilities to book `holes`. It means we have 2 reservations in system like `2014-09-01 -> 2014-09-10` and `2014-09-15 -> 2014-09-20`. How to find `free hole` from `2014-09-10 to 2014-09-15`? – Lari13 Sep 02 '14 at 11:55
  • The trick here really is to use < and > and not <= and >=. Thank me later – Fandango68 Aug 03 '23 at 02:00
10

Taking your example range of 06/06/1983 to 18/06/1983 and assuming you have columns called start and end for your ranges, you could use a clause like this

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

i.e. check the start of your test range is before the end of the database range, and that the end of your test range is after or on the start of the database range.

Paul Dixon
  • 295,876
  • 54
  • 310
  • 348
5

If your RDBMS supports the OVERLAP() function then this becomes trivial -- no need for homegrown solutions. (In Oracle it apparantly works but is undocumented).

Matthew Schinckel
  • 35,041
  • 6
  • 86
  • 121
David Aldridge
  • 51,479
  • 8
  • 68
  • 96
  • 1
    Epic solution. Works fine. This is the syntax for 2 date ranges (s1,e1) and (s2,e2) in Oracle : select 1 from dual where (s1,e1) overlaps (s2,e2); – ihebiheb Nov 16 '15 at 08:16
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;
ZippyV
  • 12,540
  • 3
  • 37
  • 52
0

In your expected results you say

06/06/1983 to 18/06/1983 = IN LIST

However, this period does not contain nor is contained by any of the periods in your table (not list!) of periods. It does, however, overlap the period 10/06/1983 to 14/06/1983.

You may find the Snodgrass book (http://www.cs.arizona.edu/people/rts/tdbbook.pdf) useful: it pre-dates mysql but the concept of time hasn't changed ;-)

onedaywhen
  • 55,269
  • 12
  • 100
  • 138
0

Try This on 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
  • 11
  • 2
0

Another method by using BETWEEN sql statement

Periods included :

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

Periods excluded :

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

I created function to deal with this problem in MySQL. Just convert the dates to seconds before use.

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
  • 893
  • 7
  • 11
0

Look into the following example. It will helpful for you.

    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
-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) )
Chad
  • 1,708
  • 1
  • 25
  • 44
Gio
  • 1
  • Welcome to Stack Overflow! Thank you for this code snippet, which may provide some immediate help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its educational value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with similar, but not identical, questions. Please [edit] your answer to add explanation, and give an indication of what limitations and assumptions apply. – Toby Speight Jun 22 '17 at 16:19