0

I have a table containing names and birth dates.
I would like to get the names corresponding to the n next upcoming birthdates. (where n is an integer)
This is not possible by using LIMIT (I don't want to limit the number of rows), neither by using BETWEEN or WHERE clause because it is not in a limited time.
Thank's for your help

Magix
  • 4,989
  • 7
  • 26
  • 50
  • 1
    I don't quite understand, if n is the number of upcoming birth dates you want to return, isn't that essentially limiting the number of rows returned? I understand your question as such: get [number of dates to return] upcoming birth dates. – wribit Jun 29 '14 at 10:38
  • Of course it is possible with `LIMIT`. You just need to remove duplicates first (hint: `GROUP BY`) – Mchl Jun 29 '14 at 10:39
  • From upcoming birthdates I assume you mean birthdays? Find n upcoming birthdays using their date of birth? – James Hull Jun 29 '14 at 11:04
  • You say that you don't want to limit the number of rows and that your query is not in a limited time. Then I don't quite understand your question. If you want n birthdates from now, then you will be querying a limited time. If you want n names, then you will be limiting the number of rows. – abl Jun 29 '14 at 11:10
  • I am not limiting the number of rows because there can be an infinite number of people born the same day. But I don't limit in time because I want the next n birth dates, not all the birth dates whithin an interval (for example, getting all the birth dates from now to 5 days in the future is NOT what I need) I didn't try anything so far because I don't get the way to do it, so I can't really try anything.... – Magix Jun 29 '14 at 17:39
  • I assume you want to return all people with the same month/day-of-month combination, regardless of the actual starting year, for the next `n` instances of month/day-of-month combinations. What happens with leap-years, what happens when somebody is born on `2/29`? You _can_ use `LIMIT` on a subquery to get the set of combinations, but this is a non-trivial query. A Calendar table will help **immensely** here, mostly for being able to use indices. What is the datatype of `birthdate`? And what have you already tried? – Clockwork-Muse Jun 30 '14 at 01:12

2 Answers2

0

The following query ...

1) first sorts distinct dob's (by default, today is sorted first).

2) selects the first dob's using the LIMIT function, and

3) lastly, selects all the users having the earliest 5 dob's.

SELECT u1.*
from USERS u1
INNER JOIN
(  
    SELECT DISTINCT 
      EXTRACT(MONTH FROM dob) dob_month,
      EXTRACT(DAY FROM dob) dob_day
    FROM users
    ORDER BY 
    CASE
      WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) = EXTRACT(DAY FROM CURDATE()) THEN 1
      WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) < EXTRACT(DAY FROM CURDATE()) THEN -1
      ELSE SIGN(EXTRACT(MONTH FROM dob) - EXTRACT(MONTH FROM CURDATE()))
    END DESC, 
    CASE
      WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) = EXTRACT(DAY FROM CURDATE()) THEN 1
      ELSE SIGN(EXTRACT(DAY FROM dob) - EXTRACT(DAY FROM CURDATE()))
    END DESC,     
    EXTRACT(MONTH FROM dob) ASC,
    EXTRACT(DAY FROM dob) ASC
  LIMIT 5
) selected_dobs
ON EXTRACT(MONTH FROM u1.dob) = selected_dobs.dob_month and EXTRACT(DAY FROM u1.dob) = selected_dobs.dob_day
ORDER BY
    CASE
      WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) = EXTRACT(DAY FROM CURDATE()) THEN 1
      WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) < EXTRACT(DAY FROM CURDATE()) THEN -1
      ELSE SIGN(EXTRACT(MONTH FROM dob) - EXTRACT(MONTH FROM CURDATE()))
    END DESC, 
    CASE
      WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) = EXTRACT(DAY FROM CURDATE()) THEN 1
      ELSE SIGN(EXTRACT(DAY FROM dob) - EXTRACT(DAY FROM CURDATE()))
    END DESC,     
    EXTRACT(MONTH FROM dob) ASC,
    EXTRACT(DAY FROM dob) ASC, 
    username ASC;

SQL Fiddle demo

NOTE

No rows are filtered out from the SELECT, because it is possible that (some or all of) the next 5 birthdays occur in the next calendar year (for e.g. today could be Dec 28 and the next 5 birthdays might occur on Dec 29, Dec 31, Jan 2, Jan 5, Jan 6).

If you would like today's birthdays to be selected at the bottom of the list (instead of the top of the list, as done in the above query), just change 1 to -1 in the ORDER BY clause, as below

ORDER BY CASE WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) = EXTRACT(DAY FROM CURDATE()) THEN -1 WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) < EXTRACT(DAY FROM CURDATE()) THEN -1 ELSE SIGN(EXTRACT(MONTH FROM dob) - EXTRACT(MONTH FROM CURDATE())) END DESC, CASE WHEN EXTRACT(MONTH FROM dob) = EXTRACT(MONTH FROM CURDATE()) AND EXTRACT(DAY FROM dob) = EXTRACT(DAY FROM CURDATE()) THEN -1 ELSE SIGN(EXTRACT(DAY FROM dob) - EXTRACT(DAY FROM CURDATE())) END DESC, EXTRACT(MONTH FROM dob) ASC, EXTRACT(DAY FROM dob) ASC

Joseph B
  • 5,519
  • 1
  • 15
  • 19
  • 1
    The OP wants to include all instances of "duplicate" birthdays, which might be more than 5 people, so this won't work. – Clockwork-Muse Jun 30 '14 at 01:17
  • @Clockwork-Muse Thanks for feedback! The revised query above selects all users with the 5 earliest birthdays. The SQL Fiddle data has an example of more than 1 user sharing a top 5 birthday. – Joseph B Jun 30 '14 at 02:48
0

First off, you're going to want a Calendar table (this particular example is for SQL Server, but should be adaptable). It's one of the most useful analysis/dimension tables, and you can often use them to get index-based access for queries that would normally be non SARG-able.

For this case we need a minimum table like this:

CREATE TABLE (isoDate DATE,
              dayOfMonth INTEGER,
              month INTEGER)

...and two indices, starting with [isoDate, dayOfMonth, month] and [dayOfMonth, month, isoDate] (or [month, dayOfMonth, isoDate]).

We can now use this to get the next 5 birthdays:

SELECT DISTINCT Next.isoDate, Next.month, Next.dayOfMonth
FROM Users
JOIN Calendar Start
  ON Start.isoDate = Users.dateOfBirth
JOIN Calendar Next
  ON Next.isoDate >= CURDATE()
     AND Next.month = Start.month
     AND Next.dayOfMonth = Start.dayOfMonth
ORDER BY Next.isoDate 
LIMIT 5

This will get all next instances of month/day-of-month combinations, looping years (and including people multiple times if necessary). Note that this doesn't move people with birthdays on February 29th (so those people won't have a birthday every year).

We can now join back again to get names:

SELECT Birthdays.isoDate AS dateOfBirth, Users.name
FROM (SELECT DISTINCT Next.isoDate, Next.month, Next.dayOfMonth
      FROM Users
      JOIN Calendar Start
        ON Start.isoDate = Users.dateOfBirth
      JOIN Calendar Next
        ON Next.isoDate >= CURDATE()
           AND Next.month = Start.month
           AND Next.dayOfMonth = Start.dayOfMonth
      ORDER BY Next.isoDate 
      LIMIT 5) Birthdays
JOIN Calendar Origin
  ON Origin.month = Birthdays.month
     AND Origin.dayOfMonth = Birthdays.dayOfMonth
JOIN Users 
  ON Users.dateOfBirth = Origin.isoDate
ORDER BY Birthdays.isoDate

And you're golden

Community
  • 1
  • 1
Clockwork-Muse
  • 12,806
  • 6
  • 31
  • 45