优化/索引时区查询

时间:2014-04-07 18:19:46

标签: postgresql timezone

我想知道是否有人对如何优化此查询有任何建议,以便它可以使用索引?我们有与数据库中的属性相关联的保留。每个属性都有一个时区集。我们想要查询的是"所有在特定日期(例如今天)开始的预订"。

相关查询片段是

reservations.start_on::Date = COALESCE((current_timestamp at time zone properties.time_zone), current_timestamp)::Date

属性表在查询的前面加入,如您所料。

正如您可能猜到的那样,问题在于时区。我们不能简单地要求start_on = {date},因为当我们说{date}时,我们的意思是{属于该属性的timezone},这可能实际上不是{date} }根据当前时间。 {date}会以{例}}的形式进入,例如4/7/2014基于我们的App服务器的时区,但如果预订的属性位于澳大利亚悉尼,我们实际上希望包含从4/8/2014开始的预订。

----编辑----

一些其他信息。

reservations.start_on存储为date类型。

properties.time_zone存储为character varying(255), default 'America/Los_Angeles'::character varying.

预订的start_on日期将存储为您在酒店时区的日期。需要解释的部分是我们在大多数情况下要求将其转移到各个属性时区的适当日期的日期。

我当然对其他方法持开放态度,但到目前为止,这是我能想到的全部内容......基本上我希望能够向DB提出几个问题:

  1. 正在开始预约"今天"?
  2. 将来有哪些预订开始日期?
  3. 过去有什么预订开始日期?
  4. 开始日期,虽然是一个日期,但显然实际上是一个时间范围,所以你不能只说start_on > now()因为时区。

    表格定义:https://gist.github.com/anonymous/10295380

    ----编辑2 ----

    我尝试切换到使用tsrange for start_on。范围从属性的时区转移到UTC。这意味着悉尼的4/11/2014 start_on存储为['4/10/2014 14:00:00', '4/11/2014 13:59:59']。我对start_on列的tsrange版本有一个gist索引。

    这似乎完美无缺,并返回正确的结果。它使用@>的gist索引。具有特定时间的查询并且速度极快。不幸的是,它没有使用gist索引进行所有范围操作......

    以下是我们的一些查询的示例EXPLAIN的一个要点(哈哈):https://gist.github.com/bdmac/10496601

    示例中显示的列arrival_day相当于start_on,但是是tsrange。我还没有删除旧的start_on列。

    一些现在很慢的查询是"即将到来的"或者"过去"保留我必须构建一个没有上限或下限的tsrange。我似乎无法弄清楚如何使用<<<<<<<<<或>>接受一个元素而不是像@>

    那样的范围

2 个答案:

答案 0 :(得分:3)

如果您确实有一个timestamp类型的列并根据当前时区解释它(部分),并且此时区可能会有所不同,则索引通常不可能 。您只能在IMMUTABLE数据...

上构建索引

更新后:

回答这些问题:

  1. “今天”开始有什么预订?
  2. 将来有哪些预订开始日期?
  3. 过去有什么预订开始日期?
  4. ...你最好存储一个timestamp with time zone。仅date不够精确。

    只要我们只对本地“今天”(由当前时区定义)感兴趣,我们需要明确保存时区。我们不关心它在世界的哪个地方发生,我们只需要一个绝对的时间来比较。

    然后,从“今天”开始预订:

    SELECT *
    FROM   reservations
    WHERE  start_on::date = current_date;
    

    但这是not sargable,因为start_on::date是一个派生表达式,我们也不能为此建立一个功能索引(没有脏技巧)因为表达式取决于当前时区而不是IMMUTABLE

    相反,与UTC时间中“我们”日的开始和结束进行比较:

    SELECT *
    FROM   reservations
    WHERE  start_on >= current_date::timestamptz
    AND    start_on < (current_date + 1)::timestamptz; -- exclude upper border
    

    现在,这个简单的索引可以支持查询:

    CREATE INDEX ON reservations (start_on);
    

    演示

    SQL Fiddle已关闭ATM。这是一个有助于理解的小演示:

    CREATE TEMP TABLE reservations (
       reservation_id serial
     , start_on timestamptz NOT NULL
     , time_zone text);    -- we don't need this
    
    INSERT INTO reservations (start_on, time_zone) VALUES
      ('2014-04-09 01:00+02', 'Europe/Vienna')
    , ('2014-04-09 23:00+02', 'Europe/Vienna')
    , ('2014-04-09 01:00+00', 'UTC')    -- the value is independent of the time zone
    , ('2014-04-09 23:00+00', 'UTC')    -- only display depends on current time zone
    , ('2014-04-09 01:00-07', 'America/Los_Angeles')
    , ('2014-04-09 23:00-07', 'America/Los_Angeles');
    
    SELECT start_on, time_zone 
         , start_on::timestamp             AS local_ts
         , start_on AT TIME ZONE time_zone AS ts_at_tz
         , current_date::timestamptz       AS lower_bound
         , (current_date + 1)::timestamptz AS upper_bound
    FROM   reservations
    WHERE  start_on >= current_date::timestamptz
    AND    start_on < (current_date + 1)::timestamptz;
    

    此处有更多解释和链接:
    Ignoring timezones altogether in Rails and PostgreSQL

答案 1 :(得分:0)

在这里看看我的答案,有一个解决方法,也可能适合你:

https://dba.stackexchange.com/questions/151771/postgresql-at-time-zone-construct-does-not-use-index/151776#151776