【问题标题】:PostgreSQL AT TIME ZONE using JOIN tablePostgreSQL AT TIME ZONE 使用 JOIN 表
【发布时间】:2015-09-28 14:39:52
【问题描述】:

在使用连接表时如何使用AT TIME ZONE 方法?

scope :not_reserved_between, ->(start_at, end_at) { ids = Reservation.joins(:ride).where(":e >= (reservations.start_at AT TIME ZONE rides.time_zone_name)::timestamp AND :s <= (reservations.end_at AT TIME ZONE rides.time_zone_name)::timestamp", s: start_at.utc, e: end_at.utc).pluck(:ride_id); where('rides.id not in (?)', ids) }

在此查询中,我使用了 reservations.start_atend_at 时间,我想将它们转换为不同的时区(连接的游乐设施表中的 time_zone_name

每个预订都有一个行程,因此会有一个time_zone_name。我想将该行程的 time_zone_name 应用到预订的 start_atend_at 邮票上。如果我这样做:

scope :not_reserved_between, ->(start_at, end_at) { ids = Reservation.joins(:ride).where(":e >= (reservations.start_at AT TIME ZONE rides.time_zone_name)::timestamp AND :s <= (reservations.end_at)::timestamp", s: start_at.utc, e: end_at.utc).pluck(:ride_id); where('rides.id not in (?)', ids) }

出于某种原因,这将起作用。第二个查询和第一个原始查询的唯一区别是第二个查询没有AT TIME ZONE rides.time_zone_name for reservations.ends_at

这是表架构:

# == Schema Information
#
# Table name: reservations
#
#  id               :integer          not null, primary key
#  ride_id          :integer
#  start_at         :datetime
#  end_at           :datetime
#  created_at       :datetime
#  updated_at       :datetime
#
# == Schema Information
#
# Table name: rides
#
#  id                       :integer          not null, primary key
#  user_id                  :integer
#  latitude                 :float
#  longitude                :float
#  created_at               :datetime
#  updated_at               :datetime
#  utc_offset               :integer
#  time_zone_name           :string(255)
#

所有日期时间都存储在 PG (PostgreSQL 9.3.5) 中的 UTC 零中。我的目标是将“start_at”和“end_at”时间戳传递给我的范围,并返回正确的游乐设施。游乐设施可以存储在不同的时区(它们有一个用于 utc_offset 和 time_zone_name 的列,time_zone_name 是“America/Los_Angeles”等),并且属于它们的预订(ride has_many reservations)的开始和结束时间存储在 UTC 零也是。

我的目标是传递一个日期时间(没有时区)作为范围的“start_at”和“end_at”参数。这是一个示例:我想查找所有在上午 8 点至上午 9 点相对于行程(预订的行程)时区没有预订的行程。这意味着我不能只在数据库中搜索与两个特定开始和结束时间戳的冲突,因为那只会找到与那个确切时间的冲突。此外,此方法可能会给我“错误冲突”:如果我搜索两个特定的开始和结束时间戳(例如 2015 年 7 月 10 日上午 8 点 PST - 2015 年 7 月 10 日上午 9 点 PST),我最终可能会与发生在在同一时间,但是它们发生在不同的相对时间(预订可能与太平洋标准时间 2015 年 7 月 10 日上午 8 点 - 太平洋标准时间 2015 年 7 月 10 日上午 9 点在同一时间戳发生,但是由于该游乐设施位于美国东部标准时间,因此预订不应该是由于我们正在搜索当地时间上午 8 点至 9 点之间可用的游乐设施,因此算作碰撞)。坦率地说,我将所有内容都存储在 UTC 零中作为标准,但是我会将一些时间戳视为“没有时区的日期时间”,因为它们需要从“相对时间”转换而来。

scope :not_reserved_between, ->(start_at, end_at) { ids = Reservation.joins(:ride).where(":e >= (reservations.start_at AT TIME ZONE rides.time_zone_name)::timestamp AND :s <= (reservations.end_at AT TIME ZONE rides.time_zone_name)::timestamp", s: start_at.utc, e: end_at.utc).pluck(:ride_id); where('rides.id not in (?)', ids) }

^我对上述代码的思考过程是,我将范围参数与预留 start_at 和 end_at 时间进行比较。我应该能够获取所有预订的开始和结束时间(存储在 UTC 零中),将它们转换为乘车时区的本地时间(通过使用'AT TIME ZONErides.time_zone_name'),然后'::timestamp ' 只接收没有时区的日期时间的值。这个本地时间可以与我提供给范围的“相对”UTC 零开始/结束时间进行比较。

【问题讨论】:

  • 至于您的问题:这完全取决于所涉及列的确切数据类型。表定义会有很长的路要走。您的软件版本号也不会受到影响。
  • 这也有助于了解您的预订时间和开始/结束时间是按照当地时间还是 UTC。
  • @ErwinBrandstetter - 这个问题已经更新了更多相关信息,希望这能让我的意图更清楚。
  • @MattJohnson 我已经详细说明了我是如何存储时间的,如果还有什么需要澄清的,请告诉我!
  • This will work for some reason. 第一条语句究竟是如何不起作用的?您是否得到错误或不正确的结果? :e:s 到底传递了什么?我假设 Ruby 中的 datetime 映射到 Postgres 中的 timestamp,而不是 timestamptz?查看实际的 Postgres 表定义(在 psql 中使用 \d reservations 得到的)和发送到 Postgres 服务器的实际查询 (can be logged with the setting log_statement = 'all') 将是理想的。

标签: ruby-on-rails postgresql timezone


【解决方案1】:

Rails ActiveRecord 将所有日期时间和时间戳(在 Rails 中同义)转换为 UTC,以避免在数据库中执行任何与时区相关的逻辑。这适用于存储和查询值。因此,您应该能够在没有任何时区逻辑的情况下定义范围:

self.not_reserved_between(start_at, end_at)
  includes(:reservations).where.not("tsrange(reservations.start_at, reservations.end_at, '[]') && tsrange(?, ?, '[]')", start_at, end_at)
end

请注意,我已将 PostgreSQL 的 tsrange 函数用于非分区时间范围,并且最后一个参数中的 '[]' 表示它们是包含范围,即端点是范围。对于独占范围,请使用'()'

【讨论】:

    猜你喜欢
    • 2011-03-14
    • 1970-01-01
    • 2021-11-12
    • 2012-04-04
    • 1970-01-01
    • 1970-01-01
    • 2018-10-20
    • 2021-11-30
    • 1970-01-01
    相关资源
    最近更新 更多