问题
I have to ask another question about datediff in Firebird. I don`t know how to get correct result in this case: worker x has two contract of employment, first in the period 1988-09-15 to 2000-03-16, second from 2000-03-16 to 2005-02-28. The result that I want to get is like this 16 years, 5 months and 3 days, because the result of first is 11 years, 6 months and 1 day, and the second result is 4 years, 11 months and 2 days. Has anyone can tell me how to do this in firebird. Most I would like to know how from sum of months (17 months) can I do 5 months, and other 12 months add to value of year. Now I have SQL like this:
SELECT
a.id_contact,
sum(floor(datediff(day, a.DATE_FROM, a.DATE_TO)/365.25)) as YEAR,
mod(sum(mod(floor(datediff(day, a.DATE_FROM, a.DATE_TO)/30.41),12)),12) as MTH
FROM KP a
group by a.id_contact
and then I get 5 months, but I don`t have 12 months add to value of year. Please help me...
回答1:
You should sum days first then sum the result and then calculate Y, M, D
SELECT
KP3.id_contact
, (KP3.D2-KP3.D1) / (12*31) AS Y
, ((KP3.D2-KP3.D1) - ((KP3.D2-KP3.D1) / (12*31)) * 12 * 31) / 31 AS M
, CAST(MOD((KP3.D2-KP3.D1) - (((KP3.D2-KP3.D1) / (12*31)) * 12 * 31), 31) AS INTEGER) AS D
FROM
(SELECT
KP2.id_contact, SUM(KP2.D1) AS D1, SUM(KP2.D2) AS D2
FROM
(
SELECT
KP.id_contact, DATEDIFF(MONTH, KP.DATE_FROM, KP.DATE_TO) / 12 AS Y, CAST(MOD(DATEDIFF(MONTH, KP.DATE_FROM, KP.DATE_TO), 12) AS INTEGER) AS M
, EXTRACT(YEAR FROM KP.DATE_FROM)*12*31+EXTRACT(MONTH FROM KP.DATE_FROM)*31+EXTRACT(DAY FROM KP.DATE_FROM) D1
, EXTRACT(YEAR FROM KP.DATE_TO)*12*31+EXTRACT(MONTH FROM KP.DATE_TO)*31+EXTRACT(DAY FROM KP.DATE_TO) D2
FROM
KP
) AS KP2
GROUP BY KP2.id_contact
) AS KP3
回答2:
The proper approach seems to measure the DAYS spent on both assignments, then sum those dates, then convert it into inherently non-precise give-or-take streeet-talk-like form of years-months-days. More on this below
Borrowing the conversion query from Livius, and adjusting coefficients to more realistic, that will develop as that:
https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=2fba0ace6a70ae16a167ec838642dc28
Here, step by step we move, building up from simple blocks into more and more complex ones, which finally gives us 16 years and 5 months and 2 days:
select rdb$get_context('SYSTEM', 'ENGINE_VERSION') as version from rdb$database;
| VERSION | | :------ | | 3.0.5 |
create table KP ( ID_CONTACT integer not null, DATE_FROM date not null, DATE_TO date not null ) -- https://stackoverflow.com/questions/51551257/how-to-get-correct-year-month-and-day-in-firebird-function-datediff
✓
create index KP_workers on KP(id_contact)
✓
insert into KP values (1, '1988-09-15', '2000-03-16')
1 rows affected
insert into KP values (1, '2000-03-16', '2005-02-28')
1 rows affected
-- the sample data from https://stackoverflow.com/questions/60030543 -- might expose the rounding bug in my original formulae: -- unexpected ROUNDING UP leading to NEGATIVE value for months insert into KP values (2, '2018-02-08', '2019-12-01')
1 rows affected
insert into KP values (2, '2017-02-20', '2018-01-01')
1 rows affected
select a.*, datediff(day, a.DATE_FROM, a.DATE_TO) as DAYS_COUNT from KP a
ID_CONTACT | DATE_FROM | DATE_TO | DAYS_COUNT ---------: | :--------- | :--------- | :--------- 1 | 1988-09-15 | 2000-03-16 | 4200 1 | 2000-03-16 | 2005-02-28 | 1810 2 | 2018-02-08 | 2019-12-01 | 661 2 | 2017-02-20 | 2018-01-01 | 315
-- Original answer by Livius SELECT KP3.id_contact , KP3.D2-KP3.D1 as days_count , (KP3.D2-KP3.D1) / (12*31) AS Y , ((KP3.D2-KP3.D1) - ((KP3.D2-KP3.D1) / (12*31)) * 12 * 31) / 31 AS M , CAST(MOD((KP3.D2-KP3.D1) - (((KP3.D2-KP3.D1) / (12*31)) * 12 * 31), 31) AS INTEGER) AS D FROM (SELECT KP2.id_contact, SUM(KP2.D1) AS D1, SUM(KP2.D2) AS D2 FROM ( SELECT KP.id_contact, DATEDIFF(MONTH, KP.DATE_FROM, KP.DATE_TO) / 12 AS Y, CAST(MOD(DATEDIFF(MONTH, KP.DATE_FROM, KP.DATE_TO), 12) AS INTEGER) AS M , EXTRACT(YEAR FROM KP.DATE_FROM)*12*31+EXTRACT(MONTH FROM KP.DATE_FROM)*31+EXTRACT(DAY FROM KP.DATE_FROM) D1 , EXTRACT(YEAR FROM KP.DATE_TO)*12*31+EXTRACT(MONTH FROM KP.DATE_TO)*31+EXTRACT(DAY FROM KP.DATE_TO) D2 FROM KP ) AS KP2 GROUP BY KP2.id_contact ) AS KP3
ID_CONTACT | DAYS_COUNT | Y | M | D ---------: | :--------- | :- | :- | -: 1 | 6120 | 16 | 5 | 13 2 | 997 | 2 | 8 | 5
select ID_CONTACT, sum(DAYS_COUNT) as DAYS_COUNT from ( select a.*, datediff(day, a.DATE_FROM, a.DATE_TO) as DAYS_COUNT from KP a ) GROUP BY 1
ID_CONTACT | DAYS_COUNT ---------: | :--------- 1 | 6010 2 | 976
-- this step taken from https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=52c1e130f589ca507c9ff185b5b2346d -- based on original Livius forumla with non-exact integer coefficients -- it seems not be generating negative counts, but still shows very different results SELECT KP_DAYS.id_contact, KP_DAYS.DAYS_COUNT / (12*31) AS Y, ((KP_DAYS.DAYS_COUNT) - ((KP_DAYS.DAYS_COUNT) / (12*31)) * 12 * 31) / 31 AS M, CAST(MOD((KP_DAYS.DAYS_COUNT) - (((KP_DAYS.DAYS_COUNT) / (12*31)) * 12 * 31), 31) AS INTEGER) AS D FROM ( select ID_CONTACT, sum(DAYS_COUNT) as DAYS_COUNT from ( select a.*, datediff(day, a.DATE_FROM, a.DATE_TO) as DAYS_COUNT from KP a ) GROUP BY 1 ) as KP_DAYS
ID_CONTACT | Y | M | D ---------: | :- | :- | -: 1 | 16 | 1 | 27 2 | 2 | 7 | 15
SELECT KP_DAYS.id_contact, KP_DAYS.days_count , FLOOR(KP_DAYS.DAYS_COUNT / 365.25) AS Y , FLOOR( (KP_DAYS.DAYS_COUNT - (FLOOR(KP_DAYS.DAYS_COUNT / 365.25) * 365.25) ) / 30.5) AS M , CAST(MOD((KP_DAYS.DAYS_COUNT) - (((KP_DAYS.DAYS_COUNT) / 365.25) * 365.25), 30.5) AS INTEGER) AS D FROM ( select ID_CONTACT, sum(DAYS_COUNT) as DAYS_COUNT from ( select a.*, datediff(day, a.DATE_FROM, a.DATE_TO) as DAYS_COUNT from KP a ) GROUP BY 1 ) as KP_DAYS
ID_CONTACT | DAYS_COUNT | Y | M | D ---------: | :--------- | :- | :- | -: 1 | 6010 | 16 | 5 | 2 2 | 976 | 2 | 8 | 1
Notice, the above is still not correct mathematically. But should give a "gut feeling" of the time stamp. The question of getting EXACT AND PRECISE measure of timespan in Y-M-D form is moot.
For example, you quoted 3 days while this query gives 2 days. I see no error there. Because months and years are different from each other you just can not correctly measure time DISTANCE in months. That would be like measuring geographical distance in cities.
How many New Yorks lie between London and Paris? How many Warsaws high is Elbrus mountain? You can not have any mathematically correct answer.
Thus you can only answer with NON-PRECISE estimations. Suitable for give-or-take kind of street talk. So, any DateDiff-based query would essentially generate a perfectly valid answer of the kind of "2Y 10M give or take few days" - an answer that IS valid for this context of "just give me overall impression".
Marrying this simplicity of getting a feel of it with perfectionism of mathematical accuracy just is not possible. For example, imagine you get the span of about 6Y. Now how many leap years should you account for? In the "6Y" from 1999 to 2004 there were TWO leap years, but in the same "6Y" from 1998 to 2003 there only was ONE leap year. Which of those is correct measure for "6Y" ???
And then we have milleniums, where 2000 was leap year but 1900 was not. And same "sliding window" problem gives you volatile undefined number of leap years in timespans like "110Y". If you want to go towards layman's perception and count timespans in "years and months" - you have to agree this makes things easy, simple and imprecise by definition. And mismatch of one or few days over several years is norm, is OK
来源:https://stackoverflow.com/questions/51551257/how-to-get-correct-year-month-and-day-in-firebird-function-datediff