简体   繁体   English

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

[英]Rails query on multiple primary keys with conditions on association

Is there a way in Active Record to construct a single query that will do a conditional join for multiple primary keys? Active Record中是否有一种方法可以构造一个查询,该查询将对多个主键进行条件连接?

Say I have the following models: 说我有以下模型:

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

With that, I could generate a query to get the best run time for an athlete: 这样,我可以生成查询以获取运动员的最佳跑步时间:

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

How can I get the best run time for each athlete in a group, using a single query? 如何使用单个查询为一组中的每个运动员获得最佳跑步时间?

The following does not work, because it applies the named scopes just once to the whole array, returning the single best time for all athletes: 以下内容不起作用,因为它将命名范围仅一次应用于整个数组,并返回所有运动员的最佳时间:

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

The following works. 以下作品。 However, it is not scalable for larger numbers of Athletes, since it generates a separate query for each Athlete: 但是,它不能扩展用于更多的运动员,因为它会为每个运动员生成一个单独的查询:

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

Is there a way to generate a single query using the Active Record query interface and associations? 有没有一种方法可以使用Active Record查询界面和关联来生成单个查询

If not, can anyone suggest a SQL query pattern that I can use for find_by_SQL? 如果没有,谁能建议我可以用于find_by_SQL的SQL查询模式? I must confess I am not very strong at SQL, but if someone will point me in the right direction I can probably figure it out. 我必须承认我在SQL方面不是很强,但是如果有人指出我正确的方向,我可能会发现。

To get the Workout objects with the best time: 要以最佳时间获取锻炼对象:

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 => })

If you require just the best workout time per user then: 如果您只需要每位用户最佳的锻炼时间,则:

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

This will return a OrderedHash 这将返回一个OrderedHash

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

Edit 1 编辑1

The solution below avoids returning multiple workouts with same best time. 下面的解决方案避免了以相同的最佳时间返回多个锻炼。 This solution is very efficient if athlete_id and time columns are indexed. 如果对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]
)

Read this article to understand how this query works. 阅读本文以了解此查询的工作方式。 Last check ( workouts.id < b.id ) in the JOIN ensures only one row is returned when there are more than one matches for the best time. JOIN中的最后一项检查( workouts.id < b.id )确保在最佳时间有多个比赛时只返回一行。 When there are more than one match to the best time for an athlete, the workout with the highest id is returned(ie the last workout). 如果一个运动员的最佳时间比赛不止一次,则返回ID最高的锻炼(即最后一次锻炼)。

Certainly following will not work 当然,以下操作无效

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

Because Athlete.find([1,2,3]) returns an array and you can't call Array.workouts 因为Athlete.find([1,2,3])返回一个数组,所以您不能调用Array.workouts

You can try something like this: 您可以尝试如下操作:

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

You can edit the conditions according to your need. 您可以根据需要编辑条件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM