tags:

views:

73

answers:

5

I have a table with anniversary dates. I want a query that returns me rows of anniversaries coming up in the next 10 days. For instance:

birthdate
---------
1965-10-10
1982-05-25


SELECT birthdate FROM Anniversaries WHERE mystical_magical_mumbo_jumbo <= 10

+------------+
| birthdate  |
+------------+
| 1982-05-25 |
+------------+
1 row in set (0.01 sec)

I'd like to keep the query in the form x <= 10, because I'll use that number 10 in other parts of the query, and if I set it to a variable, I can change it once everywhere by changing the variable, and not have to re-write the query.

+1  A: 

Try this

SELECT birthdate FROM Anniversaries 
WHERE DATEDIFF(CURTIME(),birthdate) >= 0 
AND DATEDIFF(CURTIME(),birthdate) <= 10 

Check this for reference MySQL DATEDIFF

DATEDIFF(expr1,expr2)

DATEDIFF() returns expr1 – expr2 expressed as a value in days from one date to the other. expr1 and expr2 are date or date-and-time expressions. Only the date parts of the values are used in the calculation.

mysql> SELECT DATEDIFF('2007-12-31 23:59:59','2007-12-30'); -> 1

mysql> SELECT DATEDIFF('2010-11-30 23:59:59','2010-12-31'); -> -31

alejandrobog
The year is immaterial to the comparison. You're not going to retrieve any rows unless you ignore the year.
Gilbert Le Blanc
I'm getting `NULL` s when doing `SELECT DATEDIFF(CURTIME(),birthdate) FROM Anniversaries`...?
What's the type of 'birthdate', I assume is a DateTime right?
alejandrobog
It's `date`. I don't need to know what time they were born :)
So I guess with a `date` type, `NOW()` should be used instead of `CURTIME()`. Makes sense. But now I'm getting year data in my days. I supposed I could use some modulus, but that wouldn't account for leap years?
A: 

You could actually use an index if you stored the birthday separate from the birthdate. The birthday column would have the year and month, but would all have the same year (2000, for example).

Then you'd do your query like this:

SELECT birthdate
FROM Anniversaries
WHERE birthday >= DATE_ADD(MAKEDATE(2000, DAYOFYEAR(CURDATE())), INTERVAL 10 DAYS)

While the expression isn't exactly as you desired, the number 10 is still on the right side of the expression. Applying the function to the right side of the expression and keeping just the column on the left side of the expression allows MySQL to use an index on the birthday column (assuming you have an index).

Marcus Adams
A: 

Based on alejandrobog's answer:

SELECT birthdate FROM Anniversaries
WHERE DATEDIFF(MAKEDATE(YEAR(CURRENT_DATE()), DAYOFYEAR(birthdate), CURRENT_DATE()) >= 0
AND   DATEDIFF(MAKEDATE(YEAR(CURRENT_DATE()), DAYOFYEAR(birthdate), CURRENT_DATE()) <= 10
Gilbert Le Blanc
I don't think this is what the OP wants. This gives birthdays that have already happened this year, not birthdays in the next 10 days.
Ike Walker
I need to avoid these SQL trick questions. Select * the row and do the calculations in code.
Gilbert Le Blanc
A: 

MAKEDATE() creates a date from a year and the day of year, so I can use it to get old years out of the picture:

SELECT 
    anniversary
    ,  MAKEDATE( YEAR(NOW()), DAYOFYEAR(anniversary) ) AS thisyear 
FROM Anniversaries;

+-------------+-------------+
| anniversary | thisyear    | 
+-------------+-------------+
| 1978-07-29  | 2010-07-29  |
| 1959-04-17  | 2010-04-17  |
+-------------+-------------+

Then I can use DATEDIFF() calculate the days until ( or from ) then:

SELECT 
    anniversary
    , MAKEDATE( YEAR(NOW()), DAYOFYEAR(anniversary) ) AS thisyear
    , DATEDIFF( MAKEDATE(YEAR(NOW()),DAYOFYEAR(anniversary)), NOW()) as days 
FROM Anniversaries;

+-------------+-------------+------+
| anniversary | thisyear    | days | 
+-------------+-------------+------+
| 1978-07-29  | 2010-07-29  |   70 |
| 1959-04-17  | 2010-04-17  |  -33 |
+-------------+-------------+------+

Assuming NOW() is 5/20.


Edit so the above doesn't work over year rollover. One solution is to add another calculation, where the anniversary is the next year. Here I've hard-coded the date '2010-12-31', and used an OR in a HAVING clause to filter by days that match either this year or next:

SELECT birth_date
, MAKEDATE(YEAR('2010-12-31'),DAYOFYEAR(birth_date)) as anniversary
,  DATEDIFF( MAKEDATE(YEAR('2010-12-31'),DAYOFYEAR(birth_date)), '2010-12-31') as days
, MAKEDATE(YEAR(NOW())+ 1,DAYOFYEAR(birth_date)) as next_anniversary
, DATEDIFF( MAKEDATE(YEAR(NOW())+ 1,DAYOFYEAR(birth_date)), '2010-12-31') as next_days 
FROM Anniversaries 
HAVING ( ( days <= 25 AND days > 0 ) OR next_days <= 25 );

+------------+-------------+------+------------------+-----------+
| birth_date | anniversary | days | next_anniversary | next_days |
+------------+-------------+------+------------------+-----------+
| 2010-01-23 | 2010-01-23  | -342 | 2011-01-23       |        23 |
| 1975-01-11 | 2010-01-11  | -354 | 2011-01-11       |        11 |
+------------+-------------+------+------------------+-----------+
2 rows in set (0.00 sec)
See how your query works when NOW() = 2009-12-31
Gilbert Le Blanc
Well, I can't really go back in time to test it, now can I? :D
+3  A: 

As others have stated, you need to ignore the year in your comparison. The DAYOFYEAR() function is one way to do that.

Here's a quick solution off the top of my head. It will return all birthdays in the next 10 days, even if it's late December and the birthday is next year.

It DOES NOT handle leap years properly, so it will be off by 1 day for early March birthdays if this year is a leap year and the person was not born in a leap year or vice-versa. Leap years will also cause early January birthdays to show up one day off in late December sometimes. If anyone wants to add the leap year correction, feel free :)

SELECT birthdate 
FROM Anniversaries 
WHERE dayofyear(birthdate) - dayofyear(curdate()) between 0 and 10 
or dayofyear(birthdate) + 365 - dayofyear(curdate()) between 0 and 10;
Ike Walker