The Problem:
To calculate in years, days and months separately, you need to take some care, because some problems go unnoticed if you do not test with varied dates.
Calculations based on days difference give problems by days difference each month, and end up returning some strange things when converting to YYYY MM DD, such as rounding errors and broken numbers.
Also, when you have two dates in the right order, but with a final month less than initial, and/or a final day less than the initial day, you need to remember to pass on negative days and months to the months and years of the calculation, and add the days of the month in the right amount. These two dates show the problem well, test them in the proposed solutions:
Data inicial | Data final
-------------+-------------
2013-09-30 | 2014-06-28
One solution would be to use a series of IF
s for that in the SELECT
, however, the conditions would have to be calculated correctly for the number of days of each month. It is possible to solve with several IF
s, and although complex the result, works well. Fortunately Mysql has several internal functions to handle dates, saving us the boring part.
The Solution:
Mysql has the DATE_SUB()
, that already takes into account the days of each month, and to use it correctly, it was enough to extract separately the days, then the months and finally the years of the initial date, represented by CURRENT_DATE
or CURDATE()
.
I avoided the use of NOW()
to not have to deal with hours, minutes and seconds.
Follow the query:
SELECT
@calculo :=
DATE_SUB( DATE_SUB( DATE_SUB( CURRENT_DATE,
INTERVAL YEAR( DataDeAdmissao ) - 1 YEAR
), INTERVAL MONTH( DataDeAdmissao ) - 1 MONTH
), INTERVAL DAY( DataDeAdmissao ) - 1 DAY
) AS Calculo,
DataDeAdmissao,
CURRENT_DATE AS Hoje,
CONCAT(
YEAR( @calculo ) - 1,' ANOS, ',
MONTH( @calculo ) - 1,' MESES E ',
DAY( @calculo ) - 1,' DIAS'
) AS Extenso
FROM TabelaExemplo
Test the operation in this SQL Fiddle
Important remarks:
To not show "0 YEARS", "0 MONTHS" or "0 DAYS", just use the following adjustment:
In place of YEAR( @calculo ) - 1,' ANOS, '
use IF( YEAR( @calculo) > 1, CONCAT( YEAR( @calculo ) - 1,' ANOS, ' ), '' )
The same logic can be used for plural. 1 ANO
, 2 ANOS
...
If you’re going to use such a thing for labor calculations, for example, you don’t want to solve it in a SELECT. Labor calculation involves a number of extra parameters, and actually the counting has to be done following the peculiarities of the legislation.
The concept of days, years and months apart is not as absolute as it seems. When you speak of "two years apart", or "three months apart" you may or may not be considering the extra days of leap years, it depends on how this value will be used. You don’t have a solution that fits every case. Always analyze the final application that you will give to the value obtained (it seems strange, but that’s right).
Example: "one year" before a February 29th is March 1st, or February 28th of the previous year?
Still based on the previous point: remember that you can reverse order of subtractions in select above, to take care of the years, after months and days. Depending on the desired criteria, this can make a difference in critical dates and extreme cases.
No additional protections were added against reversed dates and absurd values just to avoid complicating the answer. Probably these cases and possible errors would be dealt with in the main application anyway.
You can use the functions DATEDIFF and DATE_FORMAT mysql Mysql Date and Time Functions
– lrpinheiro
Ideally, by design, you wouldn’t use DBMS for this, since the solution tends to get stuck with the DBMS in use and the result may not be as elegant as expected. If it is not possible, it is advisable to use an UDF instead of playing the whole process in the consultations.
– Paulo Freitas
If anyone is interested in an SQL Server solution: http://answall.com/a/22856/70
– Bacco