【问题标题】:Earliest date after current date without associated record没有关联记录的当前日期之后的最早日期
【发布时间】:2015-02-03 21:28:51
【问题描述】:

我有一个带有日期列的 Rails 模型 DailyAssignment,并且想查找今天之后没有关联 DailyAssignment 的第一个日期。

例如,如果我今天有实例,明天没有实例,后天有实例,则此方法应在明天返回。

如果我要在 Ruby 中执行此操作,它会是这样的:

(Date.today..1.year.since.to_date).find do |date|
  DailyAssignment.where(date: date).empty?
end

这还可以,因为它会在找到记录后终止迭代,但有两个问题:

  1. 在 Ruby 中遍历集合很慢。
  2. 除非有某种while 构造,否则我需要指定一个“结束”日期。

在 PostgreSQL 中是否有一种很好、有效的方法来做到这一点?

【问题讨论】:

  • 自从我的 postgres 天以来已经有一段时间了,但是如果您可以针对日期表进行外部联接,您不能选择 date_assignment_id 为空且 date > 今天的最小日期吗?显然,这需要访问一些日期表,我不确定是否有更惯用的 sql 方式来做到这一点。

标签: ruby-on-rails ruby postgresql


【解决方案1】:

如果可以,您应该使用自定义查询来搜索您的数据库(此类搜索在数据库中要快得多)。

如果你搜索一个时间范围内的日期,你可以使用
generate_series(timestamp, timestamp, interval)函数:

select    s
from      generate_series(?, ? + interval '1 year'), interval '1 day') s
left join daily_assignment on s = "date"
where     "date" is null
limit     1

如果你没有真正的上限,你可以使用自连接来获取下一个免费日期:

select coalesce(
  (select    c."date" + interval '1 day'
   from      daily_assignment c
   left join daily_assignment n on n."date" = c."date" + interval '1 day'
   where     c."date" > ? - interval '1 day'
   and       n."date" is null
   order by  c."date"
   limit     1),
  ? + interval '1 day'
)

? 标记表示今天的参数(您可能需要转换,具体取决于您的输入);如果您愿意,可以改用now()

P.S.:请不要使用date 作为列名,它是SQL 中的保留字,不涉及列本身。相反,您可以使用 created_atupdated_athappens_at 等名称,甚至是 at_date

【讨论】:

  • 谢谢。这正是我正在寻找的。 Rails 可以作为ActiveRecord::Base.connection.execute 的参数执行任意SQL,所以这很完美。
  • @user2272439 我从我的未绑定(第二个)查询中省略了order by 子句,请参阅更新的内容——您可能不需要在第二个查询中使用外部选择,它只会确保您始终得到下一个空闲日期,但您可以轻松地用 ruby​​ 模拟它:如果内部查询没有得到任何结果,则意味着 tomorrow 是下一个空闲日期。
【解决方案2】:

我的建议是在日期之间进行 1 次选择查询,然后循环您的结果并将它们与您选择的结果进行比较。

# select all dailyassignments
results = DailyAssignment.where("date >= from_date AND date <= to_date")

not_found_dates = []

(Date.today..1.year.since).find do |date|
  found_assignment = results.detect {|instance| instance.date == date }
  not_found_dates << date if found_assignment.nil?
end

【讨论】:

  • 这是对速度的明显改进,只需将 DB 调用移到堆栈上即可。由于 find 和 detect 在这里的功能相同,我将其写为(Date.today..1.year.since.to_date).find{|date| results.find{|instance| instance.date == date}.nil?}。然而,我仍然在 0.1 秒左右进行基准测试。如果可能的话,我想要一个 PostgresQL 解决方案。
【解决方案3】:

你可以这样试试:

def first_date_without_assignment
  assignments = DailyAssignment.select('date').where('date > ?', Date.today)
  return Date.tomorrow if assignments.empty?

  assignment_dates = assignments.map(&:date)
  date_range = (Date.tomorrow..(assignment_dates.last.advance(days: 1)).to_a

  (date_range - assignment_dates).first
end

我没有测试它,所以我可能会打错一些东西,但它可以工作。我也发现了这一点,它应该在 postgres http://www.postgresql.org/message-id/4F96EC90.6070600@encs.concordia.ca 上工作,但在 Rails 中编写可能非常困难,或者至少看起来很糟糕。

【讨论】:

  • 类似的策略,但仍然在 Ruby 中进行实际处理。我会看看你的链接。
  • 对于足够大的日期集合,这需要几秒钟。此外,倒数第二行缺少),第一行最后需要order(:date)
  • 是的,对于足够大的日期集合可能需要几秒钟。正如我所说,我没有测试它,但结束日期没有问题。我绝对同意数据库解决方案会好得多。这个解决方案只是为了展示其他方式,你可以怎么想:)
  • 仅供参考:什么是足够大的集合?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多