如何在PostgreSQL中获取两个日期之间的月份数值?

时间:2017-03-27 08:55:17

标签: sql postgresql

我有两个日期格式为无时区时间戳。

我想比较它们并获得它们之间的月份数值:

select age(NOW(), '2012-03-24 14:44:55.454041+03')

给出:

4 years 9 mons 2 days 21:00:27.165482

这里的诀窍是我需要将此结果转换为一个月的值。

所以: 要将YEARS转换为Months

select EXTRACT(YEAR FROM age) * 12 + EXTRACT(MONTH FROM age) 
                FROM age(NOW(), '2012-06-24 14:44:55.454041+03') AS t(age)

我得到57 4*12+9

我的问题是我不知道如何转换日子。 在上面的例子中,我需要在几个月内将'2 days'转换为它的值。 '2 days'不是0个月!

在30天的月份中,15天是0.5个月。

我该怎么做? 最终结果应为57.something

3 个答案:

答案 0 :(得分:3)

您可以通过以下方式进行粗略估算:

select (extract(epoch from timestamptz '2012-06-24 14:44:55.454041+03')
      - extract(epoch from timestamptz '2017-03-27 00:00:00+03'))
      / extract(epoch from interval '30.44 days') rough_estimation

(您可以使用extract(epoch from interval '1 month')除以进行更粗略的估算。)

原始公式的问题在于它旨在提供两个日期之间的完整月份差异。如果您想要计算天数,则会出现一个有趣的问题:在您的示例中,结果应为57 months2 days 21:00:27.165482。但是在2 days 21:00:27.165482部分计算在哪个月?平均长度的月份(30.44 days)?如果您想要准确,请注意,在您的示例中,差异实际上仅为56 months,加上7 days中的2012-06差不多30天)和27 days 2017-0331天)。你应该问自己的问题:是否真的值得一个先进的公式,它考虑两个范围内的每个月的天数?

编辑:为了完整性,这是一个功能,可以考虑两个范围:

create or replace function abs_month_diff(timestamptz, timestamptz)
  returns numeric
  language sql
  stable
as $func$
  select extract(year from age)::numeric * 12 + extract(month from age)::numeric
             + (extract(epoch from (lt + interval '1 month' - l))::numeric / extract(epoch from (lt + interval '1 month' - lt))::numeric)
             + (extract(epoch from (g - gt))::numeric / extract(epoch from (gt + interval '1 month' - gt))::numeric)
             - case when gt <= l or lt = l then 1 else 0 end
  from   least($1, $2) l,
         greatest($1, $2) g,
         date_trunc('month', l) lt,
         date_trunc('month', g) gt,
         age(gt, l)
$func$;

(注意:如果您使用timestamp代替timestamptz,则此函数为immutable而非stable。就像date_trunc函数一样。)< / p>

所以:

select age('2017-03-27 00:00:00+03', '2012-06-24 14:44:55.454041+03'),
       abs_month_diff('2017-03-27 00:00:00+03', '2012-06-24 14:44:55.454041+03');

将产生:

 age                                   | abs_month_diff
---------------------------------------+-------------------------
 4 years 9 mons 2 days 09:15:04.545959 | 57.05138456751051843959

http://rextester.com/QLABV31257(过时)

编辑:功能已更正,以便在差异少于一个月时生成准确的结果。

见f.ex:

set time zone 'utc';

select abs_month_diff('2017-02-27 00:00:00+03', '2017-02-24 00:00:00+03'), 3.0 / 28,
       abs_month_diff('2017-03-27 00:00:00+03', '2017-03-24 00:00:00+03'), 3.0 / 31,
       abs_month_diff('2017-04-27 00:00:00+03', '2017-04-24 00:00:00+03'), 3.0 / 30,
       abs_month_diff('2017-02-27 00:00:00+00', '2017-03-27 00:00:00+00'), 2.0 / 28 + 26.0 / 31;

http://rextester.com/TIYQC5325(过时)

编辑2 :此功能基于以下公式,计算一个月的长度:

select (mon + interval '1 month' - mon)
from   date_trunc('month', now()) mon

这甚至会考虑DST更改。 F.ex.在我的国家/地区昨天有一个DST更改(2017-03-26),所以今天(2017-03-27)上述查询报告:30 days 23:00:00

编辑3 :功能再次得到纠正(感谢@Jonathan注意到边缘情况的边缘情况)。

http://rextester.com/JLG68351

答案 1 :(得分:1)

这应该做:

select (EXTRACT(YEAR FROM age) * 12 + EXTRACT(MONTH FROM age) + EXTRACT(DAY FROM age) / 30)::numeric 
FROM age(NOW(), '2012-06-24 14:44:55.454041+03') AS t(age)

您还可以添加ROUND()以使其更漂亮:

select ROUND((EXTRACT(YEAR FROM age) * 12 + EXTRACT(MONTH FROM age) + EXTRACT(DAY FROM age) / 30)::numeric,2) as Months
FROM age(NOW(), '2012-06-24 14:44:55.454041+03') AS t(age)

答案 2 :(得分:1)

您可以使用与此类似的内容获得近似值:

SELECT A, B*12+C-1+E/30 MONTHSBETWEEN 
FROM (
SELECT current_timestamp A, EXTRACT(YEAR FROM current_timestamp) B, EXTRACT(MONTH FROM current_timestamp) C, EXTRACT(DAYS FROM current_timestamp) E
    ) X;
用这样的东西

或更好的精度:

SELECT A, B*12+C-1+E/ DATE_PART('days', 
        DATE_TRUNC('month', A) 
        + '1 MONTH'::INTERVAL 
        - '1 DAY'::INTERVAL
    ) MONTHSBETWEEN 
FROM (
SELECT current_timestamp A, EXTRACT(YEAR FROM current_timestamp) B, EXTRACT(MONTH FROM current_timestamp) C, EXTRACT(DAYS FROM current_timestamp) E
    ) X;