Rails 查询具有关联条件的多个主键

Posted

技术标签:

【中文标题】Rails 查询具有关联条件的多个主键【英文标题】:Rails query on multiple primary keys with conditions on association 【发布时间】:2011-03-03 05:31:26 【问题描述】:

在 Active Record 中是否有一种方法可以构造单个查询来对多个主键进行条件连接?

假设我有以下模型:

Class Athlete < ActiveRecord::Base
  has_many :workouts
end

Class Workout < ActiveRecord::Base
  belongs_to :athlete
  named_scope :run, :conditions => :type => "run"
  named_scope :best, :order => "time", :limit => 1
end

这样,我可以生成一个查询来为运动员获取最佳跑步时间:

  Athlete.find(1).workouts.run.best

如何使用单个查询为组中的每个运动员获得最佳跑步时间?

以下方法不起作用,因为它只将命名范围应用于整个数组一次,返回所有运动员的最佳时间:

Athlete.find([1,2,3]).workouts.run.best

以下作品。但是,它无法针对大量运动员进行扩展,因为它会为每个运动员生成单独的查询:

[1,2,3].collect |id| Athlete.find(id).workouts.run.best

有没有办法使用 Active Record 查询界面和关联生成单个查询

如果没有,谁能推荐一个我可以用于 find_by_SQL 的 SQL 查询模式?我必须承认我在 SQL 方面不是很擅长,但如果有人能指出我正确的方向,我可能会弄清楚。

【问题讨论】:

【参考方案1】:

要获得最佳时间的锻炼对象:

athlete_ids = [1,2,3]
# Sanitize the SQL as we need to substitute the bind variable
# this query will give duplicates
join_sql    = Workout.send(:santize_sql, [ 
    "JOIN (
      SELECT a.athlete_id, max(a.time) time 
      FROM   workouts a
      WHERE  a.athlete_id IN (?)
      GROUP BY a.athlete_id
    ) b ON b.athlete_id = workouts.athlete_id AND b.time = workouts.time", 
    athlete_ids])


Workout.all(:joins => join_sql, :conditions => :athlete_id => )

如果您只需要每位用户的最佳锻炼时间,那么:

Athlete.max("workouts.time", :include => :workouts, :group => "athletes.id", 
 :conditions => :athlete_id => [1,2,3]))

这将返回一个 OrderedHash

1 => 300, 2 => 60, 3 => 120

编辑 1

以下解决方案可避免以相同的最佳时间返回多个锻炼。如果对 athlete_idtime 列进行索引,则此解决方案非常有效。

Workout.all(:joins => "LEFT OUTER JOIN workouts a 
  ON workouts.athlete_id  = a.athlete_id AND 
     (workouts.time < b.time OR workouts.id < b.id)",
  :conditions => ["workouts.athlete_id = ? AND b.id IS NULL", athlete_ids]
)

阅读此article 以了解此查询的工作原理。 JOIN 中的最后一次检查 (workouts.id &lt; b.id) 确保在最佳时间有多个匹配项时仅返回一行。当一个运动员的最佳时间有多个匹配时,返回具有最高 id 的锻炼(即最后一次锻炼)。

【讨论】:

谢谢。我收到完整对象查询的 SQL 语法错误。但是,我认为您通过 GROUP BY 子句为我指明了正确的方向。我将借此机会更聪明地使用 GROUP BY 和 HAVING 处理复杂的续集连接。 很好,成功了,谢谢。虽然语法错误很有帮助,因为它迫使我阅读一些 mysql 教程。是时候停止盲目依赖 Rails 并在底层数据库查询上变得聪明了。 关于完整查询的一个问题。假设我有大量的锻炼和运动员。使用右表(完整的 Workout 对象)、左表内部(例如,作为 SELECT 的 WHERE 条件)或同时使用这两种情况下的运动员 ID 条件,查询会更有效吗? 由答案更新,看看吧。 非常好,谢谢。事实证明,相同的模式对于优化我遇到的其他几个查询问题很有用。【参考方案2】:

当然跟随是行不通的

运动员.find([1,2,3]).workouts.run.best

因为 Athlete.find([1,2,3]) 返回一个数组,你不能调用 Array.workouts

你可以试试这样的:

Workout.find(:first, :joins => [:athlete], :conditions => "athletes.id IN (1,2,3)", :order => 'workouts.time DESC')

您可以根据需要编辑条件。

【讨论】:

你说得对,我的伪代码很草率。实际上,我为 Workout 创建了另一个命名范围,由 调用,它带有一个 lambda 条件来接收一组运动员 ID。然后我可以调用 Workout.all.by([1,2,3]).run.best。但这不起作用,只返回所有运动员的最佳时间,而不是每个运动员的最佳时间。 根据我的测试,您的建议做同样的事情:返回所有运动员的单一最佳时间,而不是每个运动员的单独最佳时间。不过,我们非常感谢您的帮助。

以上是关于Rails 查询具有关联条件的多个主键的主要内容,如果未能解决你的问题,请参考以下文章

ActiveRecord - 查询关联中最后一个元素的条件

Rails N + 1查询问题时获取与where条件关联的记录

在access查询设计中,啥情况下要把查询的两个表关联起来

一张表多个字段是另一张表的主键,关联查询语句

Rails 3 - 具有连接条件的多个数据库

Rails 中的多个查询条件 - 如果它们存在。