在Rails中,如何根据关系中的项目数或关于关系的条件返回一组记录?

时间:2009-12-23 17:28:19

标签: sql ruby-on-rails activerecord

我正在编写一个Rails应用程序,其中我有两个模型:机器模型和MachineUpdate模型。 Machine模型有许多MachineUpdates。 MachineUpdate具有日期/时间字段。我正在尝试检索具有以下条件的所有计算机记录:

  1. 机器型号在过去两周内没有MachineUpdate, OR
  2. 机器型号从未进行任何更新。
  3. 目前,我正在使用命名范围完成#1:

    named_scope :needs_updates,
                :include => :machine_updates,
                :conditions => ['machine_updates.date < ?', UPDATE_THRESHOLD.days.ago]
    

    但是,这只会获得至少有一次更新的机器型号。我还想检索已经 no 更新的机器模型。如何修改needs_updates以便它返回的项目也符合该标准?

3 个答案:

答案 0 :(得分:3)

一种解决方案是引入counter_cache:

# add a machine_updates_count integer database column (with default 0)
# and add this to your Machine model:
counter_cache :machine_updates_count

然后将OR machine_updates_count = 0添加到SQL条件中。

但是,您也可以使用LEFT JOIN来解决没有计数器缓存的问题:

named_scope :needs_updates,
  :select => "machines.*, MAX(machine_updates.date) as last_update",
  :joins  => "LEFT JOIN machine_updates ON machine_updates.machine_id = machines.id",
  :group  => "machines.id",
  :having => ["last_update IS NULL OR last_update < ?", lambda{ UPDATE_THRESHOLD.seconds.ago }]

左连接是必要的,这样您就可以确定您正在查看最新的MachineUpdate(具有MAX日期的MachineUpdate)。

另请注意,您必须将条件放在lambda中,以便每次运行查询时对其进行评估。否则,它将仅评估(当您的模型在应用程序启动时加载时),并且您将无法找到自应用程序启动以来需要更新的计算机。

更新:

此解决方案适用于MySQL和SQLite,但不适用于PostgreSQL。 Postgres不允许在SELECT子句中命名未在GROUP BY子句中使用的列(请参阅discussion)。我非常不熟悉PostgreSQL,但我确实让它按预期工作:

named_scope :needs_updates, lambda{
  cols = Machine.column_names.collect{ |c| "\"machines\".\"#{c}\"" }.join(",")
  {
    :select => cols,
    :group  => cols,
    :joins  => 'LEFT JOIN "machine_updates" ON "machine_updates"."machine_id" = "machines"."id"',
    :having => ['MAX("machine_updates"."date") IS NULL OR MAX("machine_updates"."date") < ?', UPDATE_THRESHOLD.days.ago]
  }
}

答案 1 :(得分:0)

如果您可以在表格中进行更改,则可以使用belongs_to关联的:touch方法。

例如,向名为datetime的计算机添加last_machine_update列。此后,在MachineUpdate的belongs_to中,添加:touch => :last_machine_update。这将导致该字段在上次添加或修改连接到该计算机的MachineUpdate时更新,从而无需使用指定的范围。

否则我可能会像Alex建议的那样做。

答案 2 :(得分:0)

我遇到了类似的问题。这实际上非常简单:

Machine.all(
  :include => :machine_updates,
  :conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago])

如果您正在执行命名范围,只需使用lambdas确保每次调用命名范围时重新计算日期

named_scope :needs_updates, lambda { {
  :include => :machine_updates,
  :conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago]
} }

如果您想避免返回查询中的所有MachineUpdate条记录,则需要使用:select选项仅返回所需的列

named_scope :needs_updates, lambda { {
  :select => "machines.*",
  :conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago]
} }