从now()函数减去小时

时间:2015-06-17 14:18:16

标签: sql postgresql datetime timezone date-arithmetic

我们有一台24x7全天候运行的机器。每天我都会报告每小时生产的件数。在我们的例子中,一个工作日意味着'2015-06-16 06:00:00'到'2015-06-17 06:00:00'。

这是我的代码:

select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
       count (distinct t_el_eventlog.serialnumber) as count
from t_el_eventlog
where eventtime at time zone 'CET' between '2015-06-16 06:00:00'
                                       and '2015-06-17 06:00:00'
and sourceid = '44'
group by hours
order by hours asc
  • 我的Postgres版本:“PostgreSQL 9.4.1,由Visual C ++编译构建1800,32位”

  • 我正在处理的两列数据类型:

    eventtime timestamp without time zone
    sourceid  integer NOT NULL
    
  • 时区是“欧洲/柏林”。

通过上述查询,我​​得到了我想要的信息,但我必须每天更改日期。是否可以使用now()函数作为我的情况的默认值,这样我就不必每天手动更改日期?

2 个答案:

答案 0 :(得分:6)

回答timestamp

您需要了解数据类型timestamp without time zonetimestamp with time zone的性质(名称可能是欺骗性的)。如果不这样做,请先阅读:

AT TIME ZONE构造将您的timestamp转换为timestamptz,这几乎肯定是错误的举动

where eventtime at time zone 'CET' between '2015-06-16 06:00:00'
                                       and '2015-06-17 06:00:00'

首先,它会导致性能下降。将AT TIME ZONE应用于eventtime会使表达式不是sargable 。 Postgres不能在eventtime上使用普通索引。但即使没有索引,sargable表达式也会更便宜。提供调整到表中值的边界,这样您就不必操纵每一行 你可以用匹配的表达式索引进行补偿,但无论如何它可能只是一个误解和错误。

该表达式会发生什么?

  1. AT TIME ZONE 'CET'通过附加当前时区的时区偏移量,将timestampeventtime转换为timestamptz。这会将DST(夏令时)考虑在内,因此您可以获得冬季时间戳的不同偏移量。基本上你得到了问题的答案:

    给定时区看到给定时间戳的绝对时间(UTC时间戳)是多少?

    向用户显示结果时,它将成为会话当前时区的相应本地时间戳,并附加相应的时区偏移量。 (可能与表达式中使用的相同或不同。)

  2. 右侧的字符串文字没有数据类型,因此它们的类型是从表达式中的赋值派生的。由于我们现在有效地拥有timestamptz,所以假设当前时区都是timestamptz

    在当地时间看起来像给定的时间戳时,给我当前的UTC时间戳。

    偏移量因DST规则而异。

  3. 长话短说,如果您在任何地方使用相同的时区:CET'Europe/Berlin',对于当前时间戳同样的事情,但不是历史性的或(可能的,未来的,你可以减少残余。

    表达式为 BETWEEN 第二个问题几乎总是错误timestamp值。详细说明:

    SELECT date_trunc('hour', eventtime) AS hour
         , count(DISTINCT serialnumber)  AS ct  -- sure you need distinct?
    FROM   t_el_eventlog
    WHERE  eventtime >= now()::date - interval '18 hours'
    AND    eventtime <  now()::date + interval '6 hours'
    AND    sourceid  =  44  -- don't quote the numeric literal
    GROUP  BY 1
    ORDER  BY 1;
    

    now()是SQL标准CURRENT_TIMESTAMP的Postgres实现。两者都返回timestamptz(不是timestamp!)。你也可以使用。
    now()::date相当于CURRENT_DATE。两者都取决于当前时区设置。

    您应该拥有以下格式的索引

    CREATE INDEX foo ON t_el_eventlog(sourceid, eventtime)
    

    或者,允许仅索引扫描:

    CREATE INDEX foo2 ON t_el_eventlog(sourceid, eventtime, serialnumber)
    

    如果您在不同的时区操作,事情会变得更加复杂,您应该使用timestamptz来处理所有事情。

    timestamptz

    的替代方案

    在问题更新之前,似乎时区很重要。处理不同时区时,“今天”是当前时区的功能依赖。人们往往会忘记这一点。

    要仅使用会话的当前时区设置,请使用与上面相同的查询。如果在不同的时区执行,结果实际上是错误的。 (也适用于上述情况。)

    为了保证给定时区(在您的情况下为“欧洲/柏林”)的正确结果,无论会话的当前时区设置如何,请改为使用此表达式:

        ((now() AT TIME ZONE 'Europe/Berlin')::date - interval '18 hours')
                AT TIME ZONE 'Europe/Berlin'  -- 2nd time to convert back
    

    请注意AT TIME ZONE构造为timestamp输入返回timestamptz,反之亦然。

    正如一开始所提到的,这里所有的血腥细节都是:

答案 1 :(得分:4)

您可以使用CURRENT_DATE

 select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
        count(distinct t_el_eventlog.serialnumber) as count
 from t_el_eventlog
 where eventtime at time zone 'CET' between CURRENT_DATE + interval '6 hour' and
                                            CURRENT_DATE + interval '30 hour' and
       sourceid = '44'
 group by hours
 order by hours asc;

编辑:

Erwin的评论是关于问题而不是这个答案。使用between作为日期/时间是一个坏主意。我想这应该在每一个这样做的问题中重复。但问题是,天数之间的边界的日期/时间值会被计算两次。

正确的逻辑是:

 select date_trunc('hour', t_el_eventlog.eventtime at time zone 'CET') as hours,
        count(distinct t_el_eventlog.serialnumber) as count
 from t_el_eventlog
 where eventtime at time zone 'CET' >= CURRENT_DATE + interval '6 hour' and
       eventtime at time zone 'CET' < CURRENT_DATE + interval '30 hour' and
       sourceid = '44'
 group by hours
 order by hours asc;

注意“&lt;”为第二个限制。 Here是关于此主题的好博客。虽然Aaron专注于SQL Server,但警告(以及一些解决方案)也适用于其他数据库。