timestamp
的答案
您需要了解数据类型timestamp
(没有时区的时间戳
)和timestamptz
(带有时区的时间戳
)的本质。如果您不了解,请先阅读以下内容:
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
上使用普通索引。但即使没有索引,可搜索表达式也更便宜。调整过滤器值而不是操作每个行值。
您可以通过匹配表达式索引来进行补偿,但这可能只是一种误解,而且也是错误的。
该表达式会发生什么?
AT TIME ZONE 'CET'
通过附加当前时区的时间偏移将 timestamp
值 eventtime
转换为 timestamptz
。当使用时区名称(而不是数字偏移或缩写)时,它还考虑了 DST 规则(夏令时),因此您会得到“冬季”时间戳的不同偏移量。基本上,您可以得到以下问题的答案:
在给定时区中,给定时间戳的相应 UTC 时间戳是什么?
在向用户显示结果时,它被格式化为带有会话当前时区的相应时间偏移的本地时间戳。(可能与表达式中使用的时区不同)。
右侧的字符串文字没有数据类型,因此类型是从表达式中的赋值推导出来的。由于现在是timestamptz
,所以两者都被转换为timestamptz
,假设当前会话的时区设置。
在当前会话的时区设置下,给定时间戳的相应 UTC 时间戳是什么?
偏移量可以随 DST 规则而变化。
长话短说,如果你总是使用相同的时区: CET
或 'Europe/Berlin'
- 对于现代时间戳来说是一样的,但对于历史或(可能)未来的时间戳来说不是,你可以简化代码。
第二个问题与表达式有关:BETWEEN
在使用timestamp
值时几乎总是错误的。请参见:
SELECT date_trunc('hour', eventtime) AS hour
, count(DISTINCT serialnumber) AS ct
FROM t_el_eventlog
WHERE eventtime >= now()::date - interval '18 hours'
AND eventtime < now()::date + interval '6 hours'
AND sourceid = 44
GROUP BY 1
ORDER BY 1;
now()
是Postgres实现的SQL标准CURRENT_TIMESTAMP
。两者都返回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
的替代方案
在问题更新之前,似乎时区很重要。当处理不同的时区时,“今天”是当前时区的一个功能依赖。人们往往会忘记这一点。
为了只使用会话的当前时区设置,使用与上面相同的查询。如果在不同的时区执行,则实际上结果是错误的。(适用于上述内容。)
为了保证给定时区(在您的情况下为“Europe/Berlin”)的正确结果,而不考虑会话的当前时区设置,请改用此表达式:
((now() AT TIME ZONE 'Europe/Berlin')::date - interval '18 hours')
AT TIME ZONE 'Europe/Berlin'
请注意,AT TIME ZONE
构造返回 timestamptz
输入的 timestamp
,反之亦然。
正如一开始提到的那样,所有细节都在这里: