Cleaner WHERE for current month and day in MySQL - mysql

Is there a cleaner/shorter/more readable way to add a WHERE statement for a query with the current day and month combo.
SELECT id, name, dob, (YEAR(NOW()) - YEAR(dob)) AS age
FROM people
WHERE MONTH(dob) = MONTH(NOW()) AND DAY(dob) = DAY(NOW())
For example;
WHERE dob = THISDAY()

People born on Feb. 29th generally celebrate their birthday on the 60th day, regardless of the calendar date (May 1st on non-leap years).
While not shorter, this method doesn't leave out those born on Feb. 29:
SELECT id, name, dob, (YEAR(NOW()) - YEAR(dob)) AS age
FROM people
WHERE DAYOFYEAR(curdate()) =
DAYOFYEAR(DATE_ADD(dob, INTERVAL (YEAR(NOW()) - YEAR(dob)) YEAR))
Leap years are the reason there is no simple method like DAYOFYEAR(curdate()) = DAYOFYEAR(dob).

Related

Is it possible to create a single sql statement to fetch a list of people where their birthday celebration will fall in next 60 days

I have a table where it has some name and dateofbirth.
Refence data:
ABC, 1990-11-23
BCD, 1998-10-21
CDE, 1997-05-02
DEF, 2000-10-15
EFG, 1999-01-10
FGH, 1987-01-15
GHI, 1989-12-19
HIJ, 1986-12-09
I need a SQL query where I need to get the birthday celebration dates that is going to happen during the next 60 days ordered by celebration dates.
This is the query that I used till now.
SELECT *
FROM `friends`
WHERE ( DATE_FORMAT(`dob`, '%m%d') >= DATE_FORMAT(CURDATE(), '%m%d')
AND DATE_FORMAT(`dob`, '%m%d') <= DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 60 DAY), '%m%d')
ORDER BY DATE_FORMAT(`dob`, '%m%d');
It works ok if it runs during Jan to Oct. During November and December, the condition DATE_FORMAT(dob, '%m%d') <= DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 60 DAY), '%m%d') cannot apply. For example, the resulting comparison will be like 1209 < 0131 and fails.
The result that I expect to get when executed on Dec 2, 2022 is
HIJ, 1986-12-09
GHI, 1989-12-19
EFG, 1999-01-10
FGH, 1987-01-15
How do I do this in one single query?
The thread mentioned in the comment to your question uses things like adding 365.25 days to get this to work. I think this solution might be more reliable.
You can construct this years' birthday by extracting the month and day from the date of birth, and concatenating the current year to it using STR_TO_DATE.
Then you can check using a CASE statement if this years' birthday has already passed, in which case you add a year to that birthday, because that will be the next birthday for name. Then you can check if the result of that CASE statement is BETWEEN today and 60 days from now.
I used a CTE to make it clearer to read. DBfiddle here.
WITH cte as (
SELECT
-- First determine this years (year of current date) birthday
-- by constructing it from the current year, month of birth and day of birth
STR_TO_DATE(
CONCAT(YEAR(CURDATE()),'-', MONTH(dob), '-', DAY(dob)),
'%Y-%m-%d') AS this_years_birthday,
name,
dob
FROM friends
)
SELECT cte.name, cte.dob
FROM cte
WHERE
-- If the birthday is still in this year
-- Use this years' birthday
-- else add a year to this years' birthday
-- Then filter it to be between today and 60 days from now
CASE WHEN this_years_birthday >= CURDATE()
THEN this_years_birthday
ELSE DATE_ADD(this_years_birthday, INTERVAL 1 YEAR) END
BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 60 DAY)
ORDER BY MONTH(cte.dob) DESC
name
dob
GHI
1989-12-19
HIJ
1986-12-09
EFG
1999-01-10
FGH
1987-01-15

How to handle Leap Years in anniversary for a current month in MySQL

I'm attempting to write a query that finds the user's work anniversary for the current month and considers a leap year as well (don't get an idea how to manage within the query)
Table "emp_detail":
emp_no
join_date
1
2002-06-10
2
2022-06-25
3
2020-02-29
4
2002-02-15
5
2011-02-01
So far I have tried the below query:
SELECT no,
join_date
CASE WHEN DATEADD(YY,DATEDIFF(yy,join_date,GETDATE()),join_date) < GETDATE()
THEN DATEDIFF(yy,join_date,GETDATE())
ELSE DATEDIFF(yy,join_date,GETDATE()) - 1
END AS 'anniversary'
FROM emp_detail
WHERE 'status' = 'active'
HAVING MONTH(join_date) = 06/07/08 -- ...so on
EDIT:
Expected output:
For FEBRUARY month current year 2022
emp_no
join_date
anniversary_date
3
2020-02-29
2022-02-28 (Here, want get 29 Feb 2020 leap year record with non leap year 2022)
4
2002-02-15
2022-02-15
5
2011-02-01
2022-02-01
Looking for a way to display employees with anniversary dates coming up at the start of the current month considering the leap year.
Am I going in the right direction? Any help would be great.
Most (all?) SQL engines already handle year arithmetic involving leap days the way you want: folding the leap day to the final day of February.
So, computing the employee's join_date + INTERVAL x YEAR will handle '2020-02-29' correctly. To compute that interval in MySQL/MariaDB for the current year, you may use TIMESTAMPDIFF compute the difference between EXTRACTed years yourself:
SELECT emp_no,
join_date,
join_date +
INTERVAL (EXTRACT(YEAR FROM CURDATE()) -
EXTRACT(YEAR FROM join_date)) YEAR
AS "anniversary_date_this_year",
....
You can split your problem into two steps:
filtering your "join_date" values using the current month
changing the year to your "join_date"
getting the minimum value between your updated "join_date" and the last day for that date (>> this will handle leap years kinda efficiently wrt other solutions that attempt to check for specific years every time)
WITH cte AS (
SELECT emp_no,
join_date,
STR_TO_DATE(CONCAT_WS('-',
YEAR (CURRENT_DATE()),
MONTH(join_date ),
DAY (join_date )),
'%Y-%m-%d') AS join_date_now
FROM tab
WHERE MONTH(join_date) = MONTH(CURRENT_DATE())
AND YEAR(join_date) < YEAR(CURRENT_DATE())
)
SELECT emp_no,
join_date,
LEAST(join_date_now, LAST_DAY(join_date_now)) AS anniversary_date
FROM cte
Check the demo here
Note: in the demo, since you want to look at February months and we are in July, the WHERE clause will contain an additional -5 while checking the month.
You can make use of extract function in MySQL
select * from emp_detail where extract( month from (select now())) = extract( month from join_date) and extract( year from (select now())) != extract( year from join_date);
The above query will display all employees whose work anniversary is in the current month.
For the below table:
The above query will display the following rows.
The following query also considers leap year.
If the employee has joined on Feb-29 in a leap year and the current year is a non-leap year, then the query displays Anniversary Date as 'currentYear-Feb-28'
If the employee has joined on Feb-29 in a leap year and the current year is also a leap year, then the query displays Anniversay Date as 'currentYear-Feb-29'
select empId ,
case
when ( ( extract(year from (select now()))%4 = 0 and extract(year from (select now()))%100 != 0 ) or extract(year from (select now())) % 400 = 0 ) then
cast( concat( extract(year from (select now())), '-', extract( month from join_date),'-', extract( day from join_date) ) as date)
when ( ( (extract(year from join_date) % 4 = 0 and extract(year from join_date)%100 != 0) or extract( year from join_date)%400 = 0) and extract(month from join_date) =2
and extract(day from join_date) = 29 ) then
cast( concat( cast( extract(year from (select now())) as nchar), '-02-28') as date)
else cast( concat( extract(year from (select now())), '-', extract( month from join_date),'-', extract( day from join_date) ) as date)
end as AnniversaryDate
from emp_detail
where extract(year from join_date) != extract(year from (select now()));
Emp_detail data
For this data the query will show the following rows
Further if you want to filter the date to current month only, you can make use of extract function.

people's age calcuation

I have table called person with id, name, dob 3 columns,
sample data follows:
INSERT INTO person(id,name,dob) VALUES
(1,'Ton','1964-01-02'),
(2,'Luck','1960-01-20').....
select
name, dob,
current_timestamp() as curren_date,
timestampdiff(year, dob, curdate()) as age,
timestampdiff(month, dob, curdate()) as age,
timestampdiff(day, dob, curdate()) as age,
date_format(dob, '%m'),
month(curdate()),
timestampdiff(month, date_format(dob, '%m'),month(curdate())),
DATEDIFF(dob, curdate()) AS DateDiff
from
person
So i don't know what i miss, the above code can calculate the age in years- not very accurate i believe, but ideally i want to calculate the people's age in year, month, days, e.g Ton 55 years 1 month 3 days, and also want to calculate his next birthday due date, e.g next birthday date: 11 month 10 days left etc
Thanks
You can calculate the age in years accurately as:
select (case when date_format(dob, '%m-%d') >= date_format(now(), '%m-%d')
then year(now()) - year(dob)
else year(now()) - year(dob) - 1
end)
I believe that this handles all edge cases -- such as leap years. It accurately counts the age up to the day.
I've never been a fan of years/months/days, because I think the definition is unclear. You will need to provide sample of what you want.

Upcoming birthday of users between today and x days

I am trying to make simple MySQL query to display upcoming birthdays using below query. How to exclude/remove previous(yesterday) day from showing.
CREATE TABLE users (
name VARCHAR(100),
birthday DATE
);
INSERT INTO users (name, birthday) VALUES
('kostas', '1983-10-08'),
('kostas', '1983-10-11'),
('yannis', '1979-10-13'),
('natalia', '1980-10-15'),
('kostas', '1983-10-12'),
('Moskas', '1978-10-14'),
('Rasman', '1978-10-13'),
('natalia', '1980-10-18'),
('natalia', '1980-10-16');
Query:
SELECT *
FROM
users
WHERE
birthday != '' AND ABS(DAY(CURDATE()) - DAY(birthday)) < 2
ORDER BY
DAY(birthday)
Demo: sqlfiddle
You have to use BETWEEN instead of ABS. The absolute value do not return what you want, the between 0 and "days before the birthday" (2) is the right way to get days until birthday.
You also have to use DAYOFYEAR instead of DAY and you have to reverse the order of the subtraction terms DAYOFYEAR(birthday) - DAYOFYEAR(CURDATE())
To workaround leap years birthdays, as suggested here, birthday year should be converted to current year with:
DAYOFYEAR(DATE_ADD(e.birthdate, INTERVAL (YEAR(NOW()) - YEAR(birthday)) YEAR))
The final SQL is:
SELECT *
FROM
users
WHERE
birthday != '' AND (DAYOFYEAR(DATE_ADD(birthday, INTERVAL (YEAR(NOW()) - YEAR(birthday)) YEAR))-DAYOFYEAR(CURDATE())) between 0 and 2
ORDER BY
DAY(birthday)
I'd do it this way:
Transfer the birthdays to the current year and then define the datediff you want:
SELECT *,
DATEDIFF(str_to_date(CONCAT(YEAR(curdate()), '-', MONTH(birthday), '-', DAY(birthday)), '%Y-%m-%d'), curdate()) AS `days until birthday`
FROM users
WHERE DATEDIFF(str_to_date(CONCAT(YEAR(curdate()), '-', MONTH(birthday), '-', DAY(birthday)), '%Y-%m-%d'), curdate()) BETWEEN 1 AND 5 ;
I have used this previously, hope it will help others.
SELECT name, birthday, DATE_ADD(birthday, INTERVAL IF(DAYOFYEAR(birthday) >= DAYOFYEAR(CURDATE()), YEAR(CURDATE())-YEAR(birthday), YEAR(CURDATE())-YEAR(birthday)+1) YEAR ) AS next_birthday
FROM users
WHERE birthday!= '' AND disabled = '0'
HAVING next_birthday BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 DAY)
ORDER BY next_birthday

Upcoming birthdays year bug (SQL)

this is my current sql query that gets all the upcoming birthdays for my company in the next 90 days:
SELECT
user.birthday, user.name, MONTH(user.birthday)
AS month, DAY(user.birthday) AS day
FROM user WHERE
(1 =
(FLOOR(DATEDIFF(DATE_ADD(DATE(NOW()),INTERVAL
90 DAY),birthday) / 365.25)) -
(FLOOR(DATEDIFF(DATE(NOW()),birthday)
/ 365.25)))
ORDER BY MONTH(birthday),DAY(birthday)
The problem, is that if right now is november, and there are some birthdays in january, it will display january birthdays first, then november and then december, although january birthdays already happened THIS year.
Is there a way to reorder this records in the same SQL query, so that it displays current and future months first, and THEN next year's months?
First partial solution thanks to Johan
ORDER BY ( MONTH(birthday) > MONTH(NOW()
OR ((MONTH(birthday) = MONTH(now())
AND DAY(birthday) >= DAY(NOW()) DESC
, MONTH(birthday), DAY(birthday)
Still it needs a little improvement. If a birthday already happened, it should be displayed AFTER december on the results. Example of what should be displayed assuming it is 27th june
28 june: john doe
27 december: mary wright
5 june (next year of course): mad max
I'm not sure but it seems that your birthday includes the year. If that's so than you'll have a range of birthdays per user (one for every year) and you can just select the ones within the next 90 days.
SELECT
user.birthday
, user.name
, MONTH(user.birthday) AS month
, DAY(user.birthday) AS day
FROM user
WHERE birthday BETWEEN NOW() AND DATE_ADD(NOW, INTERVAL 90 DAY)
ORDER BY Birthday DESC
If your birthday only has a month and day, your query needs to be:
SELECT
user.birthday
, user.name
, MONTH(user.birthday) AS month
, DAY(user.birthday) AS day
FROM user
WHERE STR_TO_DATE(CONCAT(YEAR(NOW()),MONTH(birthday),DAY(birthday)),'%YYYY%M%D')
BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 90 DAY) OR
STR_TO_DATE(CONCAT(YEAR(DATE_ADD(NOW(),INTERVAL 1 YEAR)),MONTH(birthday),DAY(birthday)),'%YYYY%M%D')
BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 90 DAY)
ORDER BY ( MONTH(birthday) > MONTH(NOW()
OR ((MONTH(birthday) = MONTH(now()) AND DAY(birthday) >= DAY(NOW()) DESC,
MONTH(birthday), DAY(birthday)
I believe you need to order using something that includes the year.
ORDER by date_format( date, "%d/%m/%Y" )
I am no expert but something like this may work too.
ORDER BY YEAR(birthday),MONTH(birthday),DAY(birthday)
I think you want to know if each user's birthday, brought in to the current year or the next year, falls between your range:
SELECT name, birthday
FROM (SELECT name, birthday, YEAR(NOW()) - YEAR(birthday) AS years_ago
FROM user) d
WHERE DATE_ADD(birthday, INTERVAL years_ago YEAR)
BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 90 DAY)
OR
DATE_ADD(birthday, INTERVAL (years_ago + 1) YEAR)
BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 90 DAY);
(It occurs to me that you might actually want INTERVAL 3 MONTH, rather than 90 DAY, expecially if you intend to run this query on the first of every month.)
Your query will create a full table scan.
Store an integer containing the day of the year (1st of april is going to be around 90), and compare that with the current day of the year.
I've been searching for this code, but I couldn't find a clean/simple query (that also works with leap-years (29th of february problem))
So i've made my own.
Here's the simplest code to get the upcoming birthdays for the next x days, (this query also displays the birthdays of yesterday (or you can change it to a x number of days in the past)
SELECT name, date_of_birty
FROM users
WHERE DATE(CONCAT(YEAR(CURDATE()), RIGHT(date_of_birty, 6)))
BETWEEN
DATE_SUB(CURDATE(), INTERVAL 1 DAY)
AND
DATE_ADD(CURDATE(), INTERVAL 5 DAY)