【问题标题】:Rails query on multiple primary keys with conditions on associationRails 查询具有关联条件的多个主键
【发布时间】: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 方面不是很擅长,但如果有人能指出我正确的方向,我可能会弄清楚。

【问题讨论】:

    标签: mysql ruby-on-rails activerecord


    【解决方案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。但这不起作用,只返回所有运动员的最佳时间,而不是每个运动员的最佳时间。
    • 根据我的测试,您的建议做同样的事情:返回所有运动员的单一最佳时间,而不是每个运动员的单独最佳时间。不过,我们非常感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-22
    • 2015-02-20
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多