14

What's the best (fastest) approach to compare if date is in range of dates independently from year?

table "dates":

    some_column| since        | upto        |  
    -----------|--------------|-------------|
   'foo'       | '2011-05-01' | '2013-06-01'|

Now I want this query to return 'foo'

SELECT foo FROM dates WHERE '2014-05-05' BETWEEN `since` AND `upto`

If needed, I can change type/format of stored dates in "dates" table, but I cannot change format of date which I put into query as that value is typically from another table (It's part of more complex query using joins).

gadelat
  • 1,390
  • 1
  • 17
  • 25
  • Currently they are stored in date type. I'm just saying that if it's needed, I can change that. – gadelat Sep 01 '14 at 00:27
  • 2
    date is not a 'format' . It is a type. – Mitch Wheat Sep 01 '14 at 00:28
  • Ok you got me. That's what I meant. – gadelat Sep 01 '14 at 00:28
  • You haven't explained what you meant by "independently from year" – Matt Johnson-Pint Sep 01 '14 at 00:30
  • this may help http://stackoverflow.com/questions/23446462/mysql-convert-date-to-same-date-of-current-year – andrew Sep 01 '14 at 00:31
  • I did explain that. In table there are dates with years 2011 and 2013 and in query they are compared against date with year 2014, but it's supposed to ignore year and return that yes, indeed 5th may is between 1st may and 1st june. – gadelat Sep 01 '14 at 00:34
  • Ok. And is there some reason you store them with a year to begin with? Since you'll have to strip the year away before comparing, there's not going to be any way to build an index - which means you'll have a slow table-scan at query time. – Matt Johnson-Pint Sep 01 '14 at 00:41
  • Because it's recommended all over internet to store it as date type, even if you don't need year. I can change that though, as I said. @andrew concats? that doesn't seem efficient at all – gadelat Sep 01 '14 at 00:45
  • Btw you don't need to strip year away. It depends from solution. I can store all of the dates in dates table with year 0004 (or other year) and in query just change year of foreign year to 0004. – gadelat Sep 01 '14 at 00:48
  • Ok, so when you store the date, you are fine storing it with a fixed year for all values? (I'd recommend 1000) – Matt Johnson-Pint Sep 01 '14 at 00:54
  • I'm fine to store dates with fixed year in the table "dates" as is in example. That won't help much though until we figure out how to compare it with date where year is variable – gadelat Sep 01 '14 at 01:00
  • You just replace the year in the query value. Actually, 1000 is no good, because you should use a year that is a leap year - in case you have any Feb 29th values floating around. 2000 works. – Matt Johnson-Pint Sep 01 '14 at 01:01
  • I know, that's why I proposed year 0004. If you think you figured it out, post proper answer. – gadelat Sep 01 '14 at 01:05

5 Answers5

10
SELECT foo FROM dates WHERE DATE_FORMAT('2014-01-15', "%m-%d") 
    BETWEEN DATE_FORMAT(`since`,"%m-%d") AND DATE_FORMAT(`upto`,"%m-%d")

Here's the SQL FIDDLE demo

vikrant singh
  • 2,091
  • 1
  • 12
  • 16
9

Use the DayOfYear function:

 SELECT foo FROM dates WHERE DayOfYear('2014-05-05') 
    BETWEEN DayOfYear(`since`) AND DayOfYear(`upto`)
Larry Lustig
  • 49,320
  • 14
  • 110
  • 160
  • 2
    Nice thought. I had considered that also. But leap years will through it off. March 1st is Day 60 on a non-leap year, but Day 61 on a leap year. – Matt Johnson-Pint Sep 01 '14 at 01:53
  • If MySQL doesn't adjust for this already, the alternative is to roll your own Julianizer type function (`MONTH(dt)*100 + DAY(dt)` should do it). – Larry Lustig Sep 01 '14 at 01:57
  • It's not clear for me how to integrate that julianizer to the query you posted, can you clarify please? – gadelat Sep 01 '14 at 02:23
  • Just use it, literally, in place of DayOfYear: `SELECT foo FROM dates WHERE MONTH('2014-05-05')*100 + DAY('2014-05-05') BETWEEN (MONTH(since)*100 + DAY(since)) AND (MONTH(upto)*100 + DAY(upto))` – Larry Lustig Sep 01 '14 at 02:33
  • Good idea indeed, but you also need to adjust it for the case where `since` and `upto` are not from the same year, ie. `since` is december and `upto` is january for example. Your test will select dates between january and december, and NOT between december to january. – Fenix Aoras Oct 09 '19 at 07:50
1

Since you indicated that you're fine storing an arbitrary year in your data, then you can keep using a DATE type. Then it just comes down to how to query.

  • Store the fields using a fixed arbitrary year. You should use a leap-year to accommodate the possibility of Feb 29th values. Year 2000 works nicely. (0004 won't work because it's too small for MySQL's Date type.)

  • Replace the year of any value you're querying with the same year.

  • Consider ranges that cross over the end of one year and into the next. To fully answer that, you'll need a query such as the following:

    SET @dt = cast(CONCAT('2000',RIGHT(thesourcedatevalue,6)) as DATE);
    
    SELECT some_column FROM dates
    WHERE (since <= upto AND since <= @dt AND upto >= @dt) OR
          (since > upto AND (since <= @dt OR upto >= @dt))
    

    Here is a SQL Fiddle that demonstrates

  • For performance, you should be sure that there is an index that includes the since and upto fields.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • a) 0004 is not too small for mysql's date type. b) It doesn't work and I don't even see how it could work. Condition enclosed in last pair of parenthesis doesn't make sense. Plus, I can't store "@dt" with fixed year. It can be 2000, it can be 2014, any year. I said multiple times that I can change format/type/year in the table, but not the date they are comparing against. I was hoping your suggestion will include way to replace year of "@dt" to year 2000 – gadelat Sep 01 '14 at 01:39
  • Per [these docs](http://dev.mysql.com/doc/refman/5.7/en/datetime.html) the `DATE` type's supported range is `1000-01-01` to `9999-12-31`. – Matt Johnson-Pint Sep 01 '14 at 01:47
  • The second line of the WHERE statement (including the last condition in parenthesis) is there to cover the case I described, where the range runs from December to January, crossing the year boundary. If you don't have such cases, you can remove that whole line. – Matt Johnson-Pint Sep 01 '14 at 01:50
  • I'm really not sure what you mean about the rest though. You're not *storing* `@dt` - that's the parameter that you are querying by. You should be able to easily manipulate it before querying. – Matt Johnson-Pint Sep 01 '14 at 01:51
  • Ok let's test your second condition: since=march,upto=january,"@dt"=february. So february is between january and march right? Ok. So march > january AND (march <= february OR january >= february). It fails. See? And If you thought I could manually change year of the date in query against which are compared columns since and upto, I don't get why would I use this complex query and not continue using between. Nevertheless, I explained from start that this value is typically result of JOIN and I can't change it. And I can put 0004 as year in date field just fine. – gadelat Sep 01 '14 at 02:08
  • That's correct. February would be *outside* that range, so it should fail. March-January would start in march and END in January of the next year. April would be inside the range, and would pass. – Matt Johnson-Pint Sep 01 '14 at 02:22
  • I now see what you are saying about the value coming from the result of the join. I'll update my answer to show you how you can manipulate the source value in SQL. – Matt Johnson-Pint Sep 01 '14 at 02:25
  • Updated, and modified the formula slightly. I originally was thinking that you could omit the first test, but you can't. Even if you go with the `DayOfYear` or `MMDD` approach shown in the other answers, you'll still need to consider ranges that cross the end of the year. Good luck! :) – Matt Johnson-Pint Sep 01 '14 at 17:55
0

After a brief google-look and stackoverlook, I came across theses 2 problems :

  • some year has 1 day more (29th of feb)
  • the range asked (since and upto) can belong to different years, making use of DayOfYear() a problem. This is the sql query I came up with.

Let's say we have a date '2014-05-05' to check :

SELECT foo FROM `dates` WHERE
        (
    /*     --------    since and upto are from same year    --------    */
            YEAR(`since`) = YEAR(`date_fin`)
            AND (
                (
                    MONTH(`since`) < "05"
                    OR
                    (
                        MONTH(`since`) = "05"
                        AND
                        DAY(`since`) <= "05"
                    )
                ) AND (
                    MONTH(`date_fin`) < "05"
                    OR
                    (
                        MONTH(`date_fin`) = "05"
                        AND
                        DAY(`date_fin`) >= "05"
                    )
                )
            )
        )OR(
    /*     --------    since and upto are from different year    --------    */
            YEAR(`since`) <> YEAR(`date_fin`) AND (
                (
                    MONTH(`since`) < "05"
                    OR
                    (
                        MONTH(`since`) = "05"
                        AND
                        DAY(`since`) <= "05"
                    )
                ) OR (
                    MONTH(`date_fin`) > "05"
                    OR
                    (
                        MONTH(`date_fin`) = "05"
                        AND
                        DAY(`date_fin`) >= "05"
                    )
                )
            )
        )
Fenix Aoras
  • 221
  • 3
  • 10
  • I don't see how are these a problem. dayOfYear does not care that since and upto are different years and yes, some years have different amount of days and dayOfYear should take that into account already – gadelat Oct 09 '19 at 13:04
0

I solved a similar problem in the following way:

SELECT foo FROM dates

WHERE   '0000-05-05'
BETWEEN SUBDATE(since, INTERVAL YEAR(since) YEAR)
AND     SUBDATE(upto, INTERVAL YEAR(since) YEAR)

OR      ADDDATE('0000-05-05', INTERVAL 1 YEAR)
BETWEEN SUBDATE(since, INTERVAL YEAR(since) YEAR)
AND     SUBDATE(upto, INTERVAL YEAR(since) YEAR)

First, we are resetting the all dates to zeroes, next we are looking for specific date (also resetting to zero) in between two ranges. In behind example we passed already "resetting" date from external source, but this also can be done in mysql:

WHERE   SUBDATE('2014-05-05', INTERVAL YEAR('2014-05-05') YEAR)
Alex
  • 341
  • 4
  • 9