Domanda

Hi all I have no code here to show because I can't wrap my head around a clean way to do this. In our table I have a birthday column that is a timestamp. I need to find everyone whose birthday occurs within the next 3 months.

So I know I can add 3 months to the sysdate, but how I do the date comparison while ignoring the year has me thrown for a loop.

È stato utile?

Soluzione

The challenge is to get the number of months between now and the next birthday interval. The MONTHS_BETWEEN() function combined with dividing by 12 gets us close, but we have to deal with the decimal dust and convert the remainder to something useful. I can think of two ways to deal with that: MOD() and CEIL().

Using CEIL we would take the difference between the age-to-be on the next birthday and the present age and use that in the predicate. In this case the result is in decimal years, so we need to compare to a quarter year (3/12):

where (ceil(months_between(sysdate, b.birthday)/12) - 
      months_between(sysdate, b.birthday)/12) <= 3/12;

That's a bit clunky. Alternatively, MOD would give us the remainder of months to go, so subtracting MOD from 12 gives us the months left to go:

where (12 - mod(months_between(sysdate, b.birthday),12)) <= 3;

Here it is in action with sample data:

with b as 
  (select date '1990-05-10' birthday from dual union all
  select date '1970-09-23' from dual union all
  select date '2000-04-02' from dual union all
  select date '1948-11-12' from dual)
select 
  b.birthday,
  months_between(sysdate, b.birthday) months,
  months_between(sysdate, b.birthday)/12 years,
  ceil(months_between(sysdate, b.birthday)/12) age_to_be,
  ceil(months_between(sysdate, b.birthday)/12) - months_between(sysdate, b.birthday)/12 period_to_birthday,
  mod(months_between(sysdate, b.birthday),12) mod_months,
  12 - mod(months_between(sysdate, b.birthday),12) months_diff
from b
where (12 - mod(months_between(sysdate, b.birthday),12)) <= 3;
-- Or
--where (ceil(months_between(sysdate, b.birthday)/12) - 
--      months_between(sysdate, b.birthday)/12) <= 3/12;

EDIT: I'll leave my old and incorrect answer here for reference and I'll just look the other way.

Just use ADD_MONTHS() and LAST_DAY() to find all rows where the birthday is inside of a calculated value. No need to worry about the year component, you are not working with string literals.

This example casts sysdate as a timestamp, then gets the last day of the current month and adds three months to that:

select add_months(last_day(cast(sysdate as timestamp)),3) from dual;

So using that logic, you can:

select *
from your_table
where birthday <= add_months(last_day(cast(sysdate as timestamp)),3);

Or just:

select *
from your_table
where birthday <= add_months(last_day(sysdate),3);

Oracle nicely handles the data type conversion for you.

Altri suggerimenti

The logic is a bit complicated, because of end-of-the-year wrapping. But this should work:

where (extract(month from sysdate) < 10 and
       to_char(birthday, 'MM-DD') between to_char(sysdate, 'MM-DD') and
                                          to_char(add_month(sysdate, 3), 'MM-DD')
       ) or
       (extract(month from sysdate) >= 10 and
        (to_char(birthday, 'MM-DD') >= to_char(sysdate, 'MM-DD')  or
         to_char(birthday, 'MM-DD') <= to_char(add_month(sysdate, 3), 'MM-DD')
        )
       );

A person's birthday (anniversary) in the current year, given their date of birth is:

TRUNC(SYSDATE,'Y') + (dob - TRUNC(dob,'Y'))

You can tell if this is within the next three months with something like:

SELECT 'Yes' FROM dual
WHERE TRUNC(SYSDATE,'Y') + (dob - TRUNC(dob,'Y'))
BETWEEN SYSDATE AND ADD_MONTHS(SYSDATE,3);

There is only one small problem, that is if the person was born on 31 December in a Leap year, this method has an out-by-one error when you run it in a non-leap year (i.e. it will not identify that person as begin within the next three months until the 1st of October).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top