Elegant way to select upcoming birthdays - mysql

After searching a while, I found no "good" solution to get the upcoming birthdays from a MySQL database with just one single select statement. I found many different strategies in the web but none of them worked really fine especially at the change of the year.
Now I want to share my final solution:
SELECT mem_id, mem_dateBirth, MONTH(mem_dateBirth), DAY(mem_dateBirth)
FROM rs_mem
WHERE mem_dateBirth IS NOT NULL AND
mem_dateBirth > '1900-01-01'
ORDER BY (date_format(NOW(), '%m%d') >= date_format(mem_dateBirth, '%m%d')),
date_format(mem_dateBirth, '%m%d');
The result will be a list with all the upcoming birthdays (also if the birthday is after the year change).

Your query is fine, here is another way to order your result using a CASE statement:
SELECT M.mem_id
, M.mem_dateBirth
, MONTH(mem_dateBirth) AS month
, DAY(mem_dateBirth) AS day
FROM rs_mem M
WHERE IFNULL(M.mem_dateBirth, '1900-01-01') > '1900-01-01'
ORDER BY CASE
WHEN DATE_FORMAT(M.mem_dateBirth, '%m%d') >= DATE_FORMAT(NOW(), '%m%d') THEN 1
ELSE 0
END DESC, date_format(mem_dateBirth, '%m%d');
I also modified your WHERE clause in order to make it shorter (use of IFNULL).
Hope this will help you.

MSSQL : TOP 10 is first 10 record, you can remove it for all records.
SELECT TOP 10 mem_id, mem_dateBirth, MONTH(mem_dateBirth), DAY(mem_dateBirth)
FROM rs_mem
WHERE mem_dateBirth IS NOT NULL AND
mem_dateBirth > '1900-01-01' AND
mem_dateBirth > DATE()
ORDER BY mem_dateBirth
MYSQL : limit 0,10 is first 10 record, you can remove it for all records.
SELECT mem_id, mem_dateBirth, MONTH(mem_dateBirth), DAY(mem_dateBirth)
FROM rs_mem
WHERE NOT isnull(mem_dateBirth) AND
mem_dateBirth > '1900-01-01' AND
mem_dateBirth > CURDATE()
ORDER BY mem_dateBirth limit 0,10

Related

MYSQL max() and group by error:only_full_group_by

I have question about a MySQL query that is logging error's since updating the MySQL-5.7.
The error is the "only_full_group_by" which is will spoken off on stackoverflow.
In many answers it's stated not to disable this option but improve your sql query.
The query that I'm using is returning the minimum and maximum values of a counter per hour.
SELECT MAX( counter ) AS max,
MIN( counter ) AS min,
DATE_FORMAT(date_time, '%H:%i') AS dt
FROM table1
WHERE date_time >= NOW() - INTERVAL 1 DAY
GROUP BY YEAR(date_time), MONTH(date_time), DAY(date_time), HOUR(date_time)
as I understand from the error message I'm missing one of the items from the SELECT cause in the GROUP BY cause. But however I restort/remove/add items I'm not getting the result I got before the upgrade to MySQL-5.7.
I tried to subquery the main query to improve the SQL query. But somehow I can't recreate the results.
What is it I'm missing?
MySQL isn't able to determine the functional dependence ... between the expressions in the GROUP BY clause, and the expressions in the SELECT list.
The non-aggregate expression in the SELECT list (DATE_FORMAT(date_time, '%H:%i') includes a minutes component. The GROUP BY clause is going to collapse the rows into groups by just hour. So the value of the minutes is indeterminate... we know it's going to come from some row in the group, but there's no guarantee which one.
(The question reference to ONLY_FULL_GROUP_BY seems to indicate that we've got some understanding of indeterminate values...)
The easiest (fewest) changes fix would be to wrap that expression in a MIN or MAX function.
SELECT MAX(t.counter) AS `max`
, MIN(t.counter) AS `min`
, MIN(DATE_FORMAT(t.date_time,'%H:%i')) AS `dt`
FROM table1 t
WHERE t.date_time >= NOW() - INTERVAL 1 DAY
GROUP
BY YEAR(t.date_time)
, MONTH(t.date_time)
, DAY(t.date_time)
, HOUR(t.date_time)
ORDER
BY YEAR(t.date_time)
, MONTH(t.date_time)
, DAY(t.date_time)
, HOUR(t.date_time)
If we want rows returned in a particular order, we should include an ORDER BY clause, and not rely on MySQL-specific extension or behavior of GROUP BY (which may disappear in future releases.)
It's a bit odd to be doing a GROUP BY year, month, day and not including those values in the SELECT list. (It's not invalid to do that, just kind of strange. The conditions in the WHERE clause are guaranteeing that we don't have more than 24 hours span for date_time.
My preference would to do the GROUP BY on the same expression as the non-aggregate in the SELECT list. If I ever needed more than 24 hours, I'd include the date component:
SELECT MAX(t.counter) AS `max`
, MIN(t.counter) AS `min`
, DATE_FORMAT(t.date_time,'%Y-%m-%d %H:00') + INTERVAL 0 DAY AS `dt`
FROM table1 t
WHERE t.date_time >= NOW() - INTERVAL 1 DAY
GROUP
BY DATE_FORMAT(t.date_time,'%Y-%m-%d %H:00') + INTERVAL 0 DAY
ORDER
BY DATE_FORMAT(t.date_time,'%Y-%m-%d %H:00') + INTERVAL 0 DAY
--or--
if we always know it's just one day's worth of date_time, and we only want to return the hour, then we can group by just the hour. The same expression as in the SELECT list.
SELECT MAX(t.counter) AS `max`
, MIN(t.counter) AS `min`
, DATE_FORMAT(t.date_time,'%H:00') AS `dt`
FROM table1 t
WHERE t.date_time >= NOW() - INTERVAL 1 DAY
GROUP
BY DATE_FORMAT(t.date_time,'%H:00')
, DATE_FORMAT(t.date_time,'%Y-%m-%d %H')
ORDER
BY DATE_FORMAT(t.date_time,'%Y-%m-%d %H')
SELECT MAX( counter ) AS max,
MIN( counter ) AS min,
YEAR(date_time) AS g_year,
MONTH(date_time)AS g_month,
DAY(date_time) AS g_day,
HOUR(date_time) AS g_hour
FROM table1
WHERE date_time >= NOW() - INTERVAL 1 DAY
GROUP BY g_year, g_month, g_day, g_hour
Or you can get rid of redundant data if you always do it for 1 day:
SELECT MAX( counter ) AS max,
MIN( counter ) AS min,
DAY(date_time) AS g_day,
HOUR(date_time) AS g_hour
FROM table1
WHERE date_time >= NOW() - INTERVAL 1 DAY
GROUP BY g_day, g_hour

SQL: Order by date, only month and year given - Full Group on

I have an SQL database which is setted to full group on mode. My goal is to get the amount of rows (ID's) for every month. This is why I say Group By Datum. Because of the full group mode I cannot simply say Order By created_at. Because I have selected only %m.%Y. So I can only work with Datum which is cointaing my month and year.
I already tried to connect those values like CONCAT('DATE_FORMAT(created_at, '%Y-%m'), "-01 00:00:00") but also this isn't working... Even if I turn it into a UNIX Timestamp it isn't working: UNIX_TIMESTAMP(CONCAT('DATE_FORMAT(created_at, '%Y-%m'), "-01 00:00:00"))
I even tried this one:
Order By Year(DATE_FORMAT(created_at, '%Y')), Month DATE_FORMAT(created_at, '%m') But it isn't also working...
How can I sort my result by month and year without changing the Select values?
Sofar this is my actual SQL Query. Not working either.. I am nearly trying to find a solution since 1 hour...
SELECT COUNT(ID) AS Anzahl, DATE_FORMAT(created_at, '%m.%Y') AS Datum
FROM leads
WHERE created_at >= '2015-01-01' AND created_at <= '2018-01-01'
AND shopID = 4184
GROUP BY Datum
ORDER BY UNIX_TIMESTAMP(STR_TO_DATE(Datum, '%Y-%m-01 00:00:00'))
I would appreciate any kind of help! And no, I cannot change the Select values or turn off the full_group_mode.
The following will sort by year and then month in ascending order:
order by substring(datum,4,4), substring(datum,1,2)
sqlfiddle:http://sqlfiddle.com/#!9/16fab4/3
ORDER BY DATE_FORMAT(created_at, '%Y'), date_format(created_at, '%M') desc
This will sort the results by year ascending then by month descending.
Take a look at this SQL Fiddle to see the query using MySql 5.6

MYSQL - Compare between 5 dates and order by most recent

I'm really blocked at an advanced query, if someone can help me
I have a table mysql that looks like:
customers(id, appointment_date1, appointment_date2, appointment_date3, appointment_date4)
I'm looking for a query that list me what is the next most recent appointment
Before I do this query :
SELECT CASE
WHEN (customers.appointment_date1 != '0000-00-00' AND DATE(customers.appointment_date1) >= CURDATE()) THEN customers.appointment_date1
WHEN (customers.appointment_date2 != '0000-00-00' AND DATE(customers.appointment_date2) >= CURDATE()) THEN customers.appointment_date2
WHEN (customers.appointment_date3 != '0000-00-00' AND DATE(customers.appointment_date3) >= CURDATE()) THEN customers.appointment_date3
WHEN (customers.appointment_date4 != '0000-00-00' AND DATE(customers.appointment_date4) >= CURDATE()) THEN customers.appointment_date4
END as appointment
ORDER BY appointment ASC
But it's wrong, it doesn't work correctly.
Anyone can help?
Thanks
I'd use nested mysql if() functions in select clause, like :
select *
from(
select if(date1<date2&&date1>curdate(),date1,
if(date2<date3&&date2>curdate(),date2,
if(date3>curdate(),date3, 'nothing')
)
) as date
from dates
) as dates
order by dates.date desc;
EDIT : as per Zika's comment
SELECT IF(LEAST(
IFNULL(date1,'0000-00-00'),
IFNULL(date2,'0000-00-00'),
IFNULL(date3,'0000-00-00')
)!='0000-00-00',
LEAST(
IFNULL(date1,'0000-00-00'),
IFNULL(date2,'0000-00-00'),
IFNULL(date3,'0000-00-00')
),
'aucune date'
)
FROM dates;

Sort records by; future ASC, past DESC

I want to sort records as follows:
Future/present events ASC
Past events DESC
So first today, then tomorrow, until there are no more future records.
Then I want to show the past events, but the latest first.
So far I've found a solution for the first point:
ORDER BY (
CASE WHEN ev.StartDate < CURDATE()
THEN 1
ELSE 0
END) ASC, ev.StartDate ASC
But the issue with this query is that all posts are ordered ASC, including the past posts (which need to be DESC).
How do I combine this in the CASE?
You need a slightly more complex order by:
ORDER BY (ev.StartDate < CURDATE()),
(case when ev.StartDate > CURDATE() then ev.StartDate end) ASC,
(case when ev.StartDate < CURDATE() then ev.StartDate end) DESC
You could actually do this with two clauses:
ORDER BY greatest(ev.StartDate, CURDATE()) DESC,
least(ev.StartDate, CURDATE()) ASC
But I think the first version is clearer in its intention.
I find this most straight forward, without needing complex conditional syntax:
first one ranks future before past, second one orders the future ASC, third one orders the past DESC
(second and third ones are interchangeable)
ORDER BY
(date < CURDATE()) ASC,
(greatest(date, CURDATE()) ASC,
(least(date, CURDATE()) DESC
ORDER BY
CASE WHEN (CURDATE() > ev.StartDate)
THEN datediff(CURDATE(),ev.StartDate ) --Past, older date bigger differ
ELSE datediff(ev.StartDate , CURDATE()+100) END --Future, differ from a more futrue date
I had the same requirement and found another way
ORDER BY (CURDATE()>ev.StartDate) ASC, ABS(DATEDIFF(CURDATE(),ev.StartDate))

SQL - Different "ORDER BY" values for the same field

The following code is producing a list with the non-expired rows on top, then the ones with unknown expiry date and at the end the already expired (all of them in ascending order). The problem is that I want the last block of already expired rows to be in descending order so it displays the rows that expired more recently on top of that block without altering the order of the other top blocks.
Basically, I am trying to find a way to incorporate two "ORDER BY" clauses within the same recordset...
Any ideas? Thanks
SELECT *
FROM prueba
WHERE UPPER(CONCAT(Company,Deal,keywords,Type,Expiry,Name)) LIKE UPPER(%s)
ORDER BY (CASE
WHEN prueba.Expiry = 'UNKNOWN' THEN 1
WHEN prueba.Expiry < CURRENT_DATE THEN 2
END)
, prueba.Expiry ASC
Try this
DEMO FIDDLE
SELECT * FROM t
order by case
when expiry = 'Unknown' Then 1
WHEN expiry >= CURRENT_DATE THEN 0
ELSE 2 END,
CASE WHEN expiry >= CURRENT_DATE THEN expiry END,
CASE WHEN expiry < CURRENT_DATE THEN expiry END desc
Separate those records that have expired into another SELECT clause, then UNION ALL:
(SELECT *
FROM prueba
WHERE UPPER(CONCAT(Company,Deal,keywords,Type,Expiry,Name)) LIKE UPPER(%s)
AND prueba.Expiry > CURRENT_DATE
ORDER BY prueba.Expiry DESC)
UNION ALL
(SELECT *
FROM prueba
WHERE UPPER(CONCAT(Company,Deal,keywords,Type,Expiry,Name)) LIKE UPPER(%s)
AND prueba.Expiry = 'UNKNOWN')
UNION ALL
(SELECT *
FROM prueba
WHERE UPPER(CONCAT(Company,Deal,keywords,Type,Expiry,Name)) LIKE UPPER(%s)
AND prueba.Expiry < CURRENT_DATE
ORDER BY prueba.Expiry DESC)