Rails-запрос по нескольким первичным ключам с условиями на ассоциацию - PullRequest
1 голос
/ 03 марта 2011

Есть ли способ в 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}

Есть ли способ сгенерировать одиночный запрос с использованием активной записиинтерфейс запроса и ассоциации?

Если нет, может ли кто-нибудь предложить шаблон запроса SQL, который я могу использовать для find_by_SQL?Должен признаться, я не очень силен в SQL, но если кто-то укажет мне правильное направление, я, вероятно, пойму это.

Ответы [ 2 ]

2 голосов
/ 03 марта 2011

Чтобы получить объекты тренировки с наилучшим временем:

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_id и time.

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]
)

Прочтите эту статью , чтобы понять, как работает этот запрос.Последняя проверка (workouts.id < b.id) в JOIN гарантирует, что будет возвращена только одна строка, если для лучшего времени найдено более одного совпадения.Если у спортсмена более одного совпадения с лучшим временем, возвращается тренировка с наибольшим идентификатором (т. Е. Последняя тренировка).

1 голос
/ 03 марта 2011

Конечно, следующее не будет работать

Athlete.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')

Вы можете редактировать условия в соответствии с вашими потребностями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...