我们有一台 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()
函数作为我的情况的默认值,这样我就不必每天手动更改日期?
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
上使用普通索引。即使没有索引,可控制表达式也更便宜。调整过滤器值而不是操纵每行值。这个表达会发生什么?
AT TIME ZONE 'CET'
通过附加当前时区的时间偏移量将 timestamp
值 eventtime
转换为 timestamptz
。当使用时区name(不是数字偏移量或缩写)时,这也会考虑 DST 规则(夏令时),因此您会得到“冬季”时间戳的不同偏移量。基本上你就得到了问题的答案:
给定时区中给定时间戳对应的 UTC 时间戳是什么?
向用户显示结果时,结果会被格式化为本地时间戳,并带有会话当前时区的相应时间偏移量。 (可能与表达式中使用的相同或不同)。
timestamptz
,假设会话的当前时区,两者都会转换为
timestamptz
。当前会话的时区设置的给定时间戳对应的 UTC 时间戳是多少。偏移量可能会因 DST 规则而异。
,如果您始终使用相同的时区: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;
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
,反之亦然。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.