从 now() 函数中减去小时数

Subtract hours from the now() function

我们有一台机器 运行 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

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

您可以使用 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 代替 date/times 是个坏主意。我想这应该在每个这样做的问题中重复。但问题是作为天数边界的 date/time 值被计算了两次。

正确的逻辑是:

 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;

注意第二个限制的“<”。 Here 是一个很好的关于这个主题的博客。尽管 Aaron 专注于 SQL 服务器,但警告(和一些解决方案)也适用于其他数据库。

回答 timestamp

您需要了解数据类型 timestamp (timestamp without time zone) 和 timestamptz (timestamp with time zone) 的性质。如果您不这样做,请先阅读此内容:

  • Ignoring time zones altogether in Rails and PostgreSQL

AT TIME ZONE 构造将 timestamp 转换为 timestamptz,对于您的情况,这几乎肯定是 错误的移动

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

首先,它会降低性能。将 AT TIME ZONE 应用于列 eventtime 会使表达式 而不是 sargable。 Postgres 不能在 eventtime 上使用普通索引。但即使没有索引,可搜索表达式也更便宜。调整过滤器值而不是操作每一行值。
可以用匹配的表达式索引进行补偿,但这可能只是一种误解和错误。

那个表达式会发生什么?

  1. AT TIME ZONE 'CET' 通过附加您当前时区的时间偏移将 timestampeventtime 转换为 timestamptz。当使用时区 name(不是数字偏移量或缩写)时,这也会考虑 DST 规则(夏令时),因此您会得到“冬季”时间戳的不同偏移量.基本上你得到了问题的答案:

    给定时区中给定时间戳对应的 UTC 时间戳是多少?

    向用户显示结果时,它被格式化为本地时间戳,并带有会话当前时区的相应时间偏移。 (可能与表达式中使用的相同,也可能不同)。

  2. 右边的字符串文字没有数据类型,所以类型是从表达式中的赋值派生的。由于现在是 timestamptz,两者都转换为 timestamptz,假设会话的当前时区。

    当前会话的时区设置的给定时间戳对应的 UTC 时间戳是多少。

    偏移量可能因 DST 规则而异。

长话短说,如果您总是使用相同的时区:CET'Europe/Berlin' - 对于现在的时间戳也是如此,但对于历史或(可能)未来的时间戳则不然,你可以删减。

第二个问题 表达式:BETWEEN is almost always wrong with timestamp values。参见:

  • Optimize BETWEEN date statement
  • Find overlapping date ranges in PostgreSQL

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 实现。 return timestamptz(不是 timestamp!)。您可以使用任何一种。
now()::date 等同于 CURRENT_DATE。两者都取决于当前时区设置。

您应该有一个 index 的形式:

CREATE INDEX foo ON t_el_eventlog(sourceid, eventtime)

或者,要允许仅索引扫描:

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

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

替代 timestamptz

在问题更新之前,时区似乎很重要。在处理不同时区时,"today" 是当前时区的函数依赖。人们往往会忘记这一点。

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

要保证给定时区('Europe/Berlin' 在您的情况下)的正确结果,无论会话的当前时区设置如何,请改用此表达式:

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

请注意 AT TIME ZONE 构造 returns timestamp 用于 timestamptz 输入,反之亦然。

如开头所述,所有血淋淋的细节都在这里:

  • Ignoring time zones altogether in Rails and PostgreSQL