【问题标题】:Count months between two timestamp on postgresql?计算postgresql上两个时间戳之间的月份?
【发布时间】:2012-06-13 10:07:03
【问题描述】:

我想计算两个日期之间的月数。

正在做:

SELECT TIMESTAMP '2012-06-13 10:38:40' - TIMESTAMP '2011-04-30 14:38:40';

返回: 0 年 0 月 409 天 20 小时 0 分钟 0.00 秒

等等:

SELECT extract(month from TIMESTAMP '2012-06-13 10:38:40' - TIMESTAMP '2011-04-30 14:38:40');

返回 0。

【问题讨论】:

    标签: postgresql


    【解决方案1】:

    age函数返回区间:

    age(timestamp1, timestamp2)
    

    然后我们尝试从区间中提取年份和月份并相应地添加它们:

    select extract(year from age(timestamp1, timestamp2)) * 12 +
    extract(month from age(timestamp1, timestamp2))
    

    【讨论】:

    • 很好的解决方案谢谢,如果差异超过 20 天,我还添加了这个以增加 1 个月的剩余天数:+ case when extract(days from age(timestamp1, timestamp2)) > 20然后 1 否则 0 结束
    【解决方案2】:

    age 函数给出了一个合理的间隔:

    SELECT age(TIMESTAMP '2012-06-13 10:38:40', TIMESTAMP '2011-04-30 14:38:40');
    

    返回1 year 1 mon 12 days 20:00:00,然后您可以轻松地使用EXTRACT 计算月数:

    SELECT EXTRACT(YEAR FROM age) * 12 + EXTRACT(MONTH FROM age) AS months_between
    FROM age(TIMESTAMP '2012-06-13 10:38:40', TIMESTAMP '2011-04-30 14:38:40') AS t(age);
    

    【讨论】:

    • 巧妙地添加年份和月份:)
    【解决方案3】:

    请注意,当您尝试使用日历月差时,@ram@angelin 投票最多的答案并不准确。

    select extract(year from age(timestamp1, timestamp2))*12 + extract(month from age(timestamp1, timestamp2))
    

    例如,如果您尝试这样做:

    select extract(year from age('2018-02-02'::date, '2018-03-01'::date))*12 + extract(month from age('2018-02-02'::date , '2018-03-01'::date))
    

    结果将为 0,但从 3 月到 2 月之间的月份,无论日期之间的天数如何,都应为 1。

    所以公式应该像下面这样说我们从timestamp1和timestamp2开始:

    ((year2 - year1)*12) - month1 + month2 = 两个时间戳之间的日历月

    在 pg 中将被翻译成:

    select ((extract('years' from '2018-03-01 00:00:00'::timestamp)::int -  extract('years' from '2018-02-02 00:00:00'::timestamp)::int) * 12) 
        - extract('month' from '2018-02-02 00:00:00'::timestamp)::int + extract('month' from '2018-03-01 00:00:00'::timestamp)::int;
    

    您可以创建如下函数:

    CREATE FUNCTION months_between (t_start timestamp, t_end timestamp)
    RETURNS integer
    AS $$
    select ((extract('years' from $2)::int -  extract('years' from $1)::int) * 12) 
        - extract('month' from $1)::int + extract('month' from $2)::int
    $$
    LANGUAGE SQL
    IMMUTABLE
    RETURNS NULL ON NULL INPUT;
    

    【讨论】:

      【解决方案4】:

      如果您要多次执行此操作,您可以定义以下function

      CREATE FUNCTION months_between (t_start timestamp, t_end timestamp)
          RETURNS integer
          AS $$
              SELECT
                  (
                      12 * extract('years' from a.i) + extract('months' from a.i)
                  )::integer
              from (
                  values (justify_interval($2 - $1))
              ) as a (i)
          $$
          LANGUAGE SQL
          IMMUTABLE
          RETURNS NULL ON NULL INPUT;
      

      这样你就可以了

      SELECT months_between('2015-01-01', now());
      

      【讨论】:

      • 您可能需要将now() 转换为时间戳。所以例子是select months_between('2015-01-01', now()::timestamp);
      【解决方案5】:
      SELECT date_part ('year', f) * 12
            + date_part ('month', f)
      FROM age ('2015-06-12', '2014-12-01') f
      

      结果:6 个月

      【讨论】:

        【解决方案6】:

        给出两个日期的月差

           SELECT ((extract( year FROM TIMESTAMP '2012-06-13 10:38:40' ) - extract( year FROM TIMESTAMP '2011-04-30 14:38:40' )) *12) + extract(MONTH FROM TIMESTAMP '2012-06-13 10:38:40' ) - extract(MONTH FROM TIMESTAMP '2011-04-30 14:38:40' );
        

        结果:1​​4

        必须分别提取两个日期的月份,然后提取两个结果的差异

        【讨论】:

        • 我想我们正在路上,但它仍然有问题。你的提议的结果是 14,良好的响应是 13。
        • select EXTRACT(year FROM age(TIMESTAMP '2012-06-13 10:38:40',TIMESTAMP '2011-04-30 14:38:40'))*12 + EXTRACT(month FROM 年龄(TIMESTAMP '2012-06-13 10:38:40',TIMESTAMP '2011-04-30 14:38:40'));
        • 我已经使用实施的解决方案编辑了您的答案。它应该在同行评审后可见。
        【解决方案7】:

        我曾经有同样的问题,写了这个......它很丑:

        postgres=>  SELECT floor((extract(EPOCH FROM TIMESTAMP '2012-06-13 10:38:40' ) - extract(EPOCH FROM TIMESTAMP '2005-04-30 14:38:40' ))/30.43/24/3600);
         floor 
        -------
            85
        (1 row)
        

        在此解决方案中,“一个月”被定义为 30.43 天,因此它可能会在较短的时间跨度内产生一些意想不到的结果。

        【讨论】:

        • 它可以工作,但很奇怪。我看到的另一个问题是您认为一个月是 30 天。从 2 月 3 日到 3 月 4 日为一个月。您的选择将返回 0。从 1 月 3 日到 2 月 2 日没有一个月。您的选择将返回一个。
        • 是的,不幸的是它只是大致正确(但对我来说已经足够好了)。 (对其进行了编辑以使其更清晰)。
        • 它的近似值是因为使用了30,因为月份可能更长或更短 - 时间戳之间的距离越大,这成为一个问题 - 类似的时间戳四舍五入就足够了。
        • 我已将 30 替换为 30.43,因此在较长的时间跨度上会更正确 :-)
        【解决方案8】:

        按年和月提取将按月计算:

        select extract(year from age('2016-11-30'::timestamp, '2015-10-15'::timestamp)); --> 1
        select extract(month from age('2016-11-30'::timestamp, '2015-10-15'::timestamp)); --> 1
        --> Total 13 months
        

        这种方法可以维持几个月的分数(感谢 tobixen 的除数)

        select round(('2016-11-30'::date - '2015-10-15'::date)::numeric /30.43, 1); --> 13.5 months
        

        【讨论】:

          【解决方案9】:

          这是一个 PostgreSQL 函数,其行为与 Oracle MONTHS_BETWEEN 函数完全相同。 它已经过广泛的年份(包括闰年)和超过 700k 的日期组合(包括每个月的结束)的测试。

          CREATE OR REPLACE FUNCTION months_between
          (   DATE,
              DATE
          )
          RETURNS float
          AS
          $$
              SELECT
                      (EXTRACT(YEAR FROM $1) - EXTRACT(YEAR FROM $2)) * 12
                  +   EXTRACT(MONTH FROM $1) - EXTRACT(MONTH FROM $2)
                  +   CASE
                          WHEN    EXTRACT(DAY FROM $2) = EXTRACT(DAY FROM LAST_DAY($2))
                              AND EXTRACT(DAY FROM $1) = EXTRACT(DAY FROM LAST_DAY($1))
                          THEN
                              0
                          ELSE
                              (EXTRACT(DAY FROM $1) - EXTRACT(DAY FROM $2)) / 31
                      END
              ;
          $$
          LANGUAGE SQL
          IMMUTABLE STRICT;
          

          此函数需要一个 LAST_DAY 函数(行为与 Oracle 的相同):

          CREATE OR REPLACE FUNCTION last_day
          (   DATE
          )
          RETURNS DATE
          AS
          $$
              SELECT
                  (DATE_TRUNC('MONTH', $1) + INTERVAL '1 MONTH' - INTERVAL '1 DAY')::date
              ;
          $$
          LANGUAGE SQL
          IMMUTABLE STRICT;
          

          【讨论】:

            【解决方案10】:

            试试这个解决方案:

            SELECT extract (MONTH FROM age('2014-03-03 00:00:00'::timestamp, 
            '2013-02-03 00:00:00'::timestamp)) + 12 * extract (YEAR FROM age('2014-03-03   
            00:00:00'::timestamp, '2013-02-03 00:00:00'::timestamp)) as age_in_month;
            

            【讨论】:

              【解决方案11】:
              SELECT floor(extract(days from TIMESTAMP '2012-06-13 10:38:40' - TIMESTAMP
              '2011-04-30 14:38:40')/30.43)::integer as months;
              

              给出一个近似值,但避免重复时间戳。这使用来自tobixen's answer 的提示除以 30.43 代替 30,以便在计算月份时长时间跨度不正确。

              【讨论】:

                【解决方案12】:

                我做了一个这样的函数:

                /* similar to ORACLE's MONTHS_BETWEEN */
                CREATE OR REPLACE FUNCTION ORACLE_MONTHS_BETWEEN(date_from DATE, date_to DATE)
                RETURNS REAL LANGUAGE plpgsql
                AS
                $$
                DECLARE age INTERVAL;
                declare rtn real;
                BEGIN
                    age := age(date_from, date_to);
                    rtn := date_part('year', age) * 12 + date_part('month', age) + date_part('day', age)/31::real;
                    return rtn;
                END;
                $$;
                

                Oracle 示例)

                SELECT MONTHS_BETWEEN
                (TO_DATE('2015-02-02','YYYY-MM-DD'), TO_DATE('2014-12-01','YYYY-MM-DD') ) 
                "Months" FROM DUAL;
                --result is: 2.03225806451612903225806451612903225806
                

                我的 PostgreSQL 函数示例)

                select ORACLE_MONTHS_BETWEEN('2015-02-02'::date, '2014-12-01'::date) Months;
                -- result is: 2.032258
                

                根据结果,您可以使用 CEIL()/FLOOR() 进行舍入。

                select ceil(2.032258)  --3
                select floor(2.032258) --2
                

                【讨论】:

                  【解决方案13】:

                  试试;

                  select extract(month from  age('2012-06-13 10:38:40'::timestamp, '2011-04-30 14:38:40'::timestamp)) as my_months; 
                  

                  【讨论】:

                  • 这个答案是错误的。它返回1,因为extract 只考虑月份,而不考虑你有多少个月,因为涉及的年份不同。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-11-18
                  • 2011-09-04
                  • 1970-01-01
                  • 2017-05-12
                  • 1970-01-01
                  相关资源
                  最近更新 更多