如何在使用连接表时使用 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_at
和 end_at
时间,我想将它们转换为不同的时区(time_zone_name
在加入的游乐设施表中)
每个预订都有行程,因此会有一个time_zone_name
。我想将该行程的 time_zone_name
应用于预订的 start_at
和 end_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 点相对于该游乐设施(预订的游乐设施)时区没有预订的游乐设施。这意味着我不能只在数据库中搜索与两个特定开始和结束时间戳的冲突,因为那样只会找到与那个确切时间的冲突。此方法还可以给我“错误的冲突”:如果我搜索两个特定的开始和结束时间戳(比如太平洋标准时间 7/10/2015 8am PST - 7/10/2015 9am PST),我最终可能会与发生在在同一时间,但它们发生在不同的相对时间(预订可能发生在与太平洋标准时间 7/10/2015 8am PST - 7/10/2015 9am PST 相同的时间戳,但是因为乘车位于 EST,所以预订不应该是计算为碰撞,因为我们正在搜索本地时间上午 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 ZONE rides.time_zone_name”),然后使用“::timestamp” ' 仅接收不带时区的日期时间的值。这个本地时间可以与我提供给示波器的“相对”UTC 零开始/结束时间进行比较。
最佳答案
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
非分区时间范围的函数,并且最后一个参数中的 '[]'
表示它们是包含范围的,即端点是范围的一部分。对于独占范围,请使用 '()'
。
关于ruby-on-rails - PostgreSQL AT TIME ZONE 使用 JOIN 表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31330742/