PostgreSQL:两个日期之间的天/月/年


94

我正在寻找一种在PostgreSQL中实现SQLServer函数datediff的方法。那是,

此函数返回在指定开始日期和结束日期之间交叉的指定日期部分边界的计数(作为有符号整数值)。

datediff(dd, '2010-04-01', '2012-03-05') = 704 // 704 changes of day in this interval
datediff(mm, '2010-04-01', '2012-03-05') = 23  // 23 changes of month
datediff(yy, '2010-04-01', '2012-03-05') = 2   // 2 changes of year

我知道我可以简单地通过减法来做“ dd”操作,但是对其他两个方法有什么想法吗?


Answers:


116
SELECT
  AGE('2012-03-05', '2010-04-01'),
  DATE_PART('year', AGE('2012-03-05', '2010-04-01')) AS years,
  DATE_PART('month', AGE('2012-03-05', '2010-04-01')) AS months,
  DATE_PART('day', AGE('2012-03-05', '2010-04-01')) AS days;

这将为您提供两个日期之间的完整的年,月,日...:

          age          | years | months | days
-----------------------+-------+--------+------
 1 year 11 mons 4 days |     1 |     11 |    4

更详细的datediff信息


8
由于年龄函数而+1,但我认为它的参数顺序错误:)
dcarneiro

2
select date_part('month', age('2010-04-01', '2012-03-05'));-11。这不是几个月的正确差异
smac89

1
选择date_part('month',age('2012-03-05','2010-04-01'));
Zuko

35
这并不占个月+年等,这只是做一个滚动的一天检查-即SELECT date_part('day', age('2016-10-05', '2015-10-02'))回报3
唐·钱德尔

问题是如何计算两个日期之间有多少年边界跨越和多少个月边界跨越。使用age()这种方法多年来一直是错误的。之间2014-12-312015-01-01之前的月份和年份将为0,而datediff()将为1和1。您不能仅将age()结果加1,因为age()+12015-01-01和之间将给出1 2015-01-02,而datediff()现在将为0。
AndréC. Andersen

147

只需减去它们:

SELECT ('2015-01-12'::date - '2015-01-01'::date) AS days;

结果:

 days
------
   11

2
是的,当您需要知道两个日期之间的天数时,此方法适用于PostgreSQL。
icl7126 2015年

大!仅供参考,我用它在两个日期之间按较小的范围对记录进行排序,例如:range_end - range_start ASC, id DESC
Edison Machado

1
请注意,此方法返回type interval,不能简单地将其转换为int如何使用postgres将时间间隔转换为小时数?。使用date_part/age函数组合,就像@IgorRomanchenko的答案中提到的那样,将返回类型double precision
WebWanderer

2
再三考虑,这是正确的方法。使用此方法获取两个日期之间的天数将计算月份和年份之间的天数,而接受的date_part/age答案将提供天数作为两个日期中天数部分的差。date_part('day', age('2016-9-05', '2015-10-02'))返回3
WebWanderer

1
问题不是关于天,而是关于两个日期之间必须跨越的月和年边界的数量。询问者已经知道怎么做。
安德烈C.安徒生

41

我花了一些时间寻找最佳答案,我想我已经找到了。

此sql将为您提供两个日期之间的天数,如下所示integer

SELECT
    (EXTRACT(epoch from age('2017-6-15', now())) / 86400)::int

..在今天(2017-3-28)运行时,它为我提供了:

?column?
------------
77

关于已接受答案的误解:

select age('2010-04-01', '2012-03-05'),
   date_part('year',age('2010-04-01', '2012-03-05')),
   date_part('month',age('2010-04-01', '2012-03-05')),
   date_part('day',age('2010-04-01', '2012-03-05'));

..是您将获得日期字符串各部分之间的字面差异,而不是两个日期之间的时间长短。

IE浏览器:

Age(interval)=-1 years -11 mons -4 days;

Years(double precision)=-1;

Months(double precision)=-11;

Days(double precision)=-4;


7
这应该是公认的答案。我建议的唯一调整是使用ceil()而不是将其强制转换为int(舍入部分天数,而不是截断)。
罗布(Rob)

2
好点@Rob。读者应注意这一点。我认为这是更偏爱的。我认为在大多数情况下,我希望将值截断为日期之间的实际天数,但是我可以看到在哪里也应使用四舍五入天数。
WebWanderer

2
这种方法可以通过英国夏令时绊倒了-还会有只有23小时,一年有一天,另一个25
快码彼得

哇@qcodepeter!接得好!你是绝对正确的!我们该如何解决?
WebWanderer

1
这并不能真正回答问题。问题是如何计算两个日期之间有多少年边界跨越和多少个月边界跨越。该答案仅回答有多少天,而不考虑leap年。
安德烈C.安徒生

9

几乎与您所需的功能相同(基于atiruz的回答,此处为UDF的缩短版本)

CREATE OR REPLACE FUNCTION datediff(type VARCHAR, date_from DATE, date_to DATE) RETURNS INTEGER LANGUAGE plpgsql
AS
$$
DECLARE age INTERVAL;
BEGIN
    CASE type
        WHEN 'year' THEN
            RETURN date_part('year', date_to) - date_part('year', date_from);
        WHEN 'month' THEN
            age := age(date_to, date_from);
            RETURN date_part('year', age) * 12 + date_part('month', age);
        ELSE
            RETURN (date_to - date_from)::int;
    END CASE;
END;
$$;

用法:

/* Get months count between two dates */
SELECT datediff('month', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 10 */

/* Get years count between two dates */
SELECT datediff('year', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 1 */

/* Get days count between two dates */
SELECT datediff('day', '2015-02-14'::date, '2016-01-03'::date);
/* Result: 323 */

/* Get months count between specified and current date */
SELECT datediff('month', '2015-02-14'::date, NOW()::date);
/* Result: 47 */

这个答案是不正确的。将DATEDIFF()在MS SQL函数返回1datediff(year, '2015-02-14', '2016-01-03')。这是因为你必须通过一年边界一旦这些日期之间:docs.microsoft.com/en-us/sql/t-sql/functions/...
安德烈C.安徒生

答案是正确的,因为最初的问题是关于PostgreSQL的,而不是MS SQL的。
Riki_tiki_tavi

我知道很难接受,但是最初的问题是找到“在PostgreSQL中实现SQLServer函数datediff的方法”。MS SQL Server的用户倾向于将其称为SQL Server。尝试搜索“ SQL Server”:i.imgur.com/USCHdLS.png用户希望将功能从一种特定技术移植到另一种。
安德烈C.安德森

抱歉,您是对的。看来我很着急,不明白您第一条评论的意思。答案已更新。
Riki_tiki_tavi

7
SELECT date_part ('year', f) * 12
     + date_part ('month', f)
FROM age ('2015-06-12'::DATE, '2014-12-01'::DATE) f

结果:6


1

这个问题充满误解。首先让我们充分理解问题。提问者希望得到相同的结果运行时的MS SQL Server的功能 DATEDIFF ( datepart , startdate , enddate ),其中datepart需要ddmmyy

该函数的定义是:

此函数返回在指定开始日期和结束日期之间交叉的指定日期部分边界的计数(作为有符号整数值)。

这意味着要跨越多少天边界,月边界或年边界。它们之间没有多少天,几个月或几年。这就是为什么datediff(yy, '2010-04-01', '2012-03-05')是2,而不是1。这两个日期之间的间隔不到2年,这意味着从2010年到2011年以及从2011年到2012年,已经过去了整整1年,但跨越了2年。

以下是我正确复制逻辑的最佳尝试。

-- datediff(dd`, '2010-04-01', '2012-03-05') = 704 // 704 changes of day in this interval
select ('2012-03-05'::date - '2010-04-01'::date );
-- 704 changes of day

-- datediff(mm, '2010-04-01', '2012-03-05') = 23  // 23 changes of month
select (date_part('year', '2012-03-05'::date) - date_part('year', '2010-04-01'::date)) * 12 + date_part('month', '2012-03-05'::date) - date_part('month', '2010-04-01'::date)
-- 23 changes of month

-- datediff(yy, '2010-04-01', '2012-03-05') = 2   // 2 changes of year
select date_part('year', '2012-03-05'::date) - date_part('year', '2010-04-01'::date);
-- 2 changes of year

1

我想扩展Riki_tiki_tavi的答案,并从那里获取数据。我创建了一个datediff函数,它几乎可以执行sql server的所有操作。这样我们就可以考虑任何单位。

create function datediff(units character varying, start_t timestamp without time zone, end_t timestamp without time zone) returns integer
language plpgsql
 as
 $$
DECLARE
 diff_interval INTERVAL; 
 diff INT = 0;
 years_diff INT = 0;
BEGIN
 IF units IN ('yy', 'yyyy', 'year', 'mm', 'm', 'month') THEN
   years_diff = DATE_PART('year', end_t) - DATE_PART('year', start_t);

   IF units IN ('yy', 'yyyy', 'year') THEN
     -- SQL Server does not count full years passed (only difference between year parts)
     RETURN years_diff;
   ELSE
     -- If end month is less than start month it will subtracted
     RETURN years_diff * 12 + (DATE_PART('month', end_t) - DATE_PART('month', start_t)); 
   END IF;
 END IF;

 -- Minus operator returns interval 'DDD days HH:MI:SS'  
 diff_interval = end_t - start_t;

 diff = diff + DATE_PART('day', diff_interval);

 IF units IN ('wk', 'ww', 'week') THEN
   diff = diff/7;
   RETURN diff;
 END IF;

 IF units IN ('dd', 'd', 'day') THEN
   RETURN diff;
 END IF;

 diff = diff * 24 + DATE_PART('hour', diff_interval); 

 IF units IN ('hh', 'hour') THEN
    RETURN diff;
 END IF;

 diff = diff * 60 + DATE_PART('minute', diff_interval);

 IF units IN ('mi', 'n', 'minute') THEN
    RETURN diff;
 END IF;

 diff = diff * 60 + DATE_PART('second', diff_interval);

 RETURN diff;
END;
$$;

1

@WebWanderer的答案非常接近使用SQL Server的DateDiff,但是不准确。那是因为使用了age()函数。

例如,“ 2019-07-29”和“ 2020-06-25”之间的天数应返回332,但是,使用age()函数将返回327。因为age()返回“ 10 mons 27 days”,并且每个月30天,这是不正确的。

您使用时间戳记来获得准确的结果。例如

ceil((select extract(epoch from (current_date::timestamp - <your_date>::timestamp)) / 86400))


0

这是带有输出的完整示例。psql(10.1,服务器9.5.10)。

您得到58,而不是小于30的某个值。
删除age()函数,解决了先前文章中提到的问题。

drop table t;
create table t(
    d1 date
);

insert into t values(current_date - interval '58 day');

select d1
, current_timestamp - d1::timestamp date_diff
, date_part('day', current_timestamp - d1::timestamp)
from t;

     d1     |        date_diff        | date_part
------------+-------------------------+-----------
 2018-05-21 | 58 days 21:41:07.992731 |        58

1
此答案未回答如何复制datediff(yy,'2010-04-01','2012-03-05')= 2和月份版本的问题。
安德烈C.安徒生

0

另一种解决方案,针对“年”差异的版本:

SELECT count(*) - 1 FROM (SELECT distinct(date_trunc('year', generate_series('2010-04-01'::timestamp, '2012-03-05', '1 week')))) x

    2

(1列)

和几个月的相同技巧:

SELECT count(*) - 1 FROM (SELECT distinct(date_trunc('month', generate_series('2010-04-01'::timestamp, '2012-03-05', '1 week')))) x

   23

(1列)

在现实生活中,查询中可能存在一些按小时/天/周/等分组的时间戳序列,而不是generate_series。

'count(distinct(date_trunc('month', ts)))'可以直接在选择的“左”侧处:

SELECT sum(a - b)/count(distinct(date_trunc('month', c))) FROM d

为了简洁起见,我在这里使用了generate_series()。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.