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

标签 sql postgresql

我有两个日期,格式为 Time Stamp Without Time Zone。

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

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 天” 不是 0 个月!

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

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

最佳答案

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

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 个月2 天 21:00:27.165482。但是 2 days 21:00:27.165482 部分应该在几月份计算呢?一个平均长度的月(30.44 天)?如果您想精确一点,请注意,在您的示例中,差异实际上只有 56 个月,加上 2012-06 中的近 7 天 (有 30 天)和 27 天 2017-03(有 31 天).您应该问自己的问题:是否真的值得一个高级公式来考虑两个范围结束的每个月的天数?

编辑:为了完整起见,这里有一个函数,可以考虑两个范围结束:

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 函数一样。)

所以:

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

这甚至会考虑夏令时的变化。 F.ex.在我的国家/地区,昨天(2017-03-26)发生了 DST 更改,所以今天(2017-03-27)以上查询报告:30天 23:00:00

编辑 3:再次更正函数(感谢 @Jonathan 注意到边缘情况的边缘情况)。

http://rextester.com/JLG68351

关于sql - 如何在 PostgreSQL 中获取两个日期之间的月份数值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43041943/

相关文章:

php - 如何从 Joomla 2.5 数据库列、字段名称和值中获取值

sql - 连接两个表,以便结果显示在多列中

python - 找不到页面 (404) 请求方法 : POST Request URL: http://127. 0.0.1:8000/accounts/signup/signup

linux - 将 AWK 结果分配给变量

mysql - 查询在 MySQL 5.1 中有效,但在 SQL2000 中无效

sql - 按特定标识符对行进行分组并更新组 ID 列以跟踪它们属于哪个组

mysql - 我如何在mysql中获得前5名的分数

ruby-on-rails - Rails 嵌套属性正确地将 parent_id 分配为索引,但未分配其他属性

python - 如何使用别名在 Django 中进行多表连接

ruby-on-rails - 我的 Ruby on Rails 应用程序如何在没有密码的情况下访问数据库?