Instead of transforming the columns in the WHERE
clause, do so with the constants given by the application. Those calculations only have to be done once.
We can write the condition in a short, compact form:
SELECT STR_TO_DATE(...)
FROM a
WHERE (2016, a.month, a.day, a.hour, 0, 0)
>
( YEAR(FROM_UNIXTIME(456456)),
MONTH(FROM_UNIXTIME(456456)),
DAY(FROM_UNIXTIME(456456)),
HOUR(FROM_UNIXTIME(456456)),
MINUTE(FROM_UNIXTIME(456456)),
SECOND(FROM_UNIXTIME(456456))
)
AND (2016, a.month, a.day, a.hour, 0, 0)
<
( YEAR(FROM_UNIXTIME(789789)),
MONTH(FROM_UNIXTIME(789789)),
DAY(FROM_UNIXTIME(789789)),
HOUR(FROM_UNIXTIME(789789)),
MINUTE(FROM_UNIXTIME(789789)),
SECOND(FROM_UNIXTIME(789789))
)
or in a long form, which has much more chances of using indexes effectively, in versions before 5.7:
SELECT STR_TO_DATE(...)
FROM a
WHERE ((2016 > YEAR(FROM_UNIXTIME(456456)))
OR (2016 = YEAR(FROM_UNIXTIME(456456))
AND a.month > MONTH(FROM_UNIXTIME(456456)))
OR (2016 = YEAR(FROM_UNIXTIME(456456))
AND a.month = MONTH(FROM_UNIXTIME(456456))
AND a.day > DAY(FROM_UNIXTIME(456456)))
OR (2016 = YEAR(FROM_UNIXTIME(456456))
AND a.month = MONTH(FROM_UNIXTIME(456456))
AND a.day = DAY(FROM_UNIXTIME(456456))
AND a.hour > HOUR(FROM_UNIXTIME(456456))))
AND ((2016 < YEAR(FROM_UNIXTIME(789789)))
OR (2016 = YEAR(FROM_UNIXTIME(789789))
AND a.month < MONTH(FROM_UNIXTIME(789789)))
OR (2016 = YEAR(FROM_UNIXTIME(789789))
AND a.month = MONTH(FROM_UNIXTIME(789789))
AND a.day < DAY(FROM_UNIXTIME(789789)))
OR (2016 = YEAR(FROM_UNIXTIME(789789))
AND a.month = MONTH(FROM_UNIXTIME(789789))
AND a.day = DAY(FROM_UNIXTIME(789789))
AND a.hour < HOUR(FROM_UNIXTIME(789789)))
OR (2016 = YEAR(FROM_UNIXTIME(789789))
AND a.month = MONTH(FROM_UNIXTIME(789789))
AND a.day = DAY(FROM_UNIXTIME(789789))
AND a.hour = HOUR(FROM_UNIXTIME(789789))
AND 0 < MINUTE(FROM_UNIXTIME(789789))+SECOND(FROM_UNIXTIME(789789))))
This will also allow MySQL to use indexes on this query, if available. If not already done, you should create an index as (month, day, hour)
(in this order!).