假设我希望用户能够创建包含在自己的时区中指定的时间范围(但没有日期)的记录。然后,在任何时候,当我的应用程序中发生某些事情时,它需要查找当前时间在此时间范围内的所有记录,并尊重与记录一起存储的时区。假设我有一个这样的表设置:
schedules table
id | start_time | end_time | timezone
---------------------------------------
1 | 08:00:00 | 17:00:00 | US/Pacific
2 | 06:00:00 | 13:30:00 | US/Arizona
3 | 13:00:00 | 22:00:00 | US/Eastern
... etc
start_time
和 end_time
是没有时区的时间类型。 timezone
是 varchar 类型,但我们假设它保证是 name
视图中 pg_timezone_names
列中的值。
我有一个查询,可以查找当前时间位于行的
start_time
和 end_time
之间的行:
SELECT
*
FROM
schedules
WHERE
schedules.start_time <= (CURRENT_TIMESTAMP AT TIME ZONE schedules.timezone)::time
AND schedules.end_time >= (CURRENT_TIMESTAMP AT TIME ZONE schedules.timezone)::time;
注意表达方式
(CURRENT_TIMESTAMP AT TIME ZONE schedules.timezone)::time
。此表达式旨在获取行指定时区的当前本地时间。据我所知,它应该尊重该时区的 DST 当前状态,因为 schedules.timezone
不是一个小时偏移,而是一个命名时区,这意味着 Postgres 知道在给定的该时区 DST 是打开还是关闭时间戳。
现在的问题是,我认为无论我如何索引表,这个查询都保证会进行全表扫描,这意味着该表增长得越多,它就会明显变慢。有没有更好的方法来做到这一点,保留在任何时区自动调整 DST 的能力,而不需要全表扫描?
有趣的问题!查询不能使用索引的技术原因是比较的双方都包含列引用,即它们都依赖于当前行。您需要相当恒定的东西来进行索引扫描。
但我认为这不仅仅是您编写查询的方式的错误(查询是正确的!):您想要的基本上是不可能的。美国/东部时区 13:00:00 的含义取决于当前日期。要使用索引,您必须将“美国/东部时区今天 13:00:00”与当前时间戳进行比较,因此您必须对该表达式建立索引。但是您索引的表达式无法更改其当前日期的含义。
我能想到的唯一解决方法是创建一个像这样的物化视图:
CREATE MATERIALIZED VIEW schedules_today AS
SELECT id,
(start_time + current_date) AT TIME ZONE timezone AS start_timestamp,
(end_time + current_date) AT TIME ZONE timezone AS end_timestamp
FROM schedules;
您可以在
start_timestamp
和end_timestamp
上创建索引以加快查询速度。您必须每天午夜之后刷新物化视图。
如果您使用范围类型,您可能会获得更好的性能:
CREATE MATERIALIZED VIEW schedules_today AS
SELECT id,
tstzrange(
(start_time + current_date) AT TIME ZONE timezone,
(end_time + current_date) AT TIME ZONE timezone,
'[]'
) AS start_end
FROM schedules;
CREATE INDEX ON schedules_today USING gist (start_end);
GiST 索引将加速以下查询:
SELECT * FROM schedules_today
WHERE start_end @> current_timestamp;