从 now() 函数中减去小时

问题描述 投票:0回答:2

我们有一台 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++ build 1800编译,32位”

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

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

通过上面的查询我得到了我想要的信息,但是我必须每天更改日期。是否可以使用

now()
函数作为我的情况的默认值,这样我就不必每天手动更改日期?

sql postgresql datetime timezone date-arithmetic
2个回答
26
投票

回答
timestamp

如果您不熟悉数据类型

timestamp
(
timestamp without time zone
) 和
timestamptz
(
timestamp with time zone
) 的性质,请先阅读以下内容:

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

事件时间位于“CET”时区“2015-06-16 06:00:00”之间
                                       和“2015-06-17 06:00:00”

首先,它会降低性能。将

AT TIME ZONE
应用于列
eventtime
会使表达式 not sargable。 Postgres 无法在
eventtime
上使用普通索引。即使没有索引,可控制表达式也更便宜。调整过滤器值而不是操纵每行值。
可以用匹配的表达式索引进行补偿,但这可能只是一个误解和错误。

这个表达会发生什么?

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

    给定时区中给定时间戳对应的 UTC 时间戳是什么?

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

  2. 右侧的字符串文字没有数据类型,因此类型是从表达式中的赋值派生的。由于现在是
  3. timestamptz

    ,假设会话的当前时区,两者都会转换为

    timestamptz

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

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

长话短说

,如果您始终使用相同的时区:CET

'Europe/Berlin'
- 对于当前时间戳也是如此,但对于历史或(可能)未来的时间戳则不然,您可以直接剪切残骸。

第二个问题

的表达式:BETWEEN

几乎总是错误的
timestamp
。参见:

    优化BETWEEN日期语句
  • 在 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 实现。两者都返回 
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

在问题更新之前,时区似乎很重要。当处理不同时区时,

“今天”

是当前时区的函数依赖。人们往往会忘记这一点。 要仅使用会话的当前时区设置,请使用与上面相同的查询。如果在不同的时区执行,结果实际上是错误的。 (也适用于上述内容。)

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

((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
,反之亦然。
    


6
投票
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;

注意“
Here

”是关于此主题的一个很好的博客。尽管 Aaron 专注于 SQL Server,但警告(以及一些解决方案)也适用于其他数据库。<" for the second limit.

© www.soinside.com 2019 - 2024. All rights reserved.