2013-06-18 3 views
13

У меня есть модель Student и модель Gpa. Student has_many Gpa. Как я должен сортировать студентов по их последнему созданному атрибуту gpa record value?Сортировка по последней записи has_many в Rails

ПРИМЕЧАНИЕ. Я не хочу сортировать GPA отдельного учащегося на основе созданной даты. Я хотел бы, чтобы вытащить всех студентов и сортирует их на основе самой последней записи ГПД

class Student < ActiveRecord::Base 
    has_many :gpas 
end 

@students = Student.order(...) 
+1

Какие СУБД вы используете? –

+0

Я использую ActiveRecord –

+1

Нет - PostgreSQL, Oracle, MySQL ...? –

ответ

10

предполагая ГП метку время updated_at

Student.joins(:gpas).order('gpas.updated_at DESC').uniq 

Чтобы включить студентов без ГПА

#references is rails 4; works in rails 3 without it 
Student.includes(:gpas).order('gpas.updated_at DESC').references(:gpas).uniq 

, если вы не любите distinct, что uniq создает, вы можете использовать некоторые сырые SQL

Student.find_by_sql("SELECT students.* FROM students 
INNER JOIN gpas ON gpas.student_id = students.id 
LEFT OUTER JOIN gpas AS future ON future.student_id = gpas.student_id 
    AND future.updated_at > gpas.updated_at 
WHERE future.id IS NULL ORDER BY gpas.updated_at DESC") 
# or some pretty raw arel 
gpa_table = Gpa.arel_table 
on = Arel::Nodes::On.new(
    Arel::Nodes::Equality.new(gpa_table[:student_id], Student.arel_table[:id]) 
) 
inner_join = Arel::Nodes::InnerJoin.new(gpa_table, on) 
future_gpa_table = Gpa.arel_table.alias("future") 
on = Arel::Nodes::On.new(
    Arel::Nodes::Equality.new(future_gpa_table[:student_id], gpa_table[:student_id]).\ 
    and(future_gpa_table[:updated_at].gt(gpa_table[:updated_at]) 
) 
) 
outer_join = Arel::Nodes::OuterJoin.new(future_gpa_table, on) 
# get results 
Student.joins(inner_join).joins(outer_join).where("future.id IS NULL").\ 
order('gpas.updated_at DESC') 
0

Вероятно, что-то вроде

Student.joins(:gpas).order("gpas.value DESC") 
+0

Это не работает. Я заканчиваю w/несколько экземпляров одного и того же «Студента» (потому что тогда есть несколько записей gpa). –

+0

как насчет добавления DISTINCT –

4

Я не уверен, что есть способ достижения этой цели в любом виде удобного и в основном рубинового. SQL, необходимые для эффективной реализации, вероятно, требует порядка, основанного на присоединиться - что-то вроде ...

select 
    ... 
from 
    students 
order by 
    ( select gpas.value 
     from gpas 
     where gpas.student_id = student.id 
    order by gpas.as_of_date desc 
     limit 1) 

я не уверен, если это законно в MySQL, но если вы могли бы, вероятно, просто:

Student.order("(select gpas.value from gpas where gpas.student_id = student.id order by gpas.as_of_date desc limit 1)") 

С другой стороны, кажется, что последнее значение было бы важным, поэтому вам может потребоваться выполнить обратный вызов gpas для установки «last_gpa_id» или «last_gpa_value» в таблице студентов, чтобы сделать это общее более эффективны.

Тогда, конечно, реализация была бы тривиальной.

+0

+1 Я думаю, что это должен быть принятый ответ ... – j03w

+0

Ну, я не думаю, что использование предложения 'select' в порядке заказа является очень оптимальным. Тем не менее, cache 'last_gpa_id' или' last_gpa_value' кажутся лучшим решением для меня. – j03w

+0

@ j03w зависит от оптимизатора запросов. Он имеет теоретический выбор для реализации коррелированного подзапроса в качестве объединения. Было бы интересно посмотреть план выполнения, но я ожидаю, что он будет работать очень хорошо. –

0

@students = Student.includes(:gpas).order('gpas.value DESC')

Тем не менее, важно отметить, что это будет включать студентов, которые не получили не gpas. Но вы можете легко фильтровать это с помощью @students.delete_if{ |s| s.gpas.blank? }

+0

Включает ли это право? Вы хотите присоединиться? –

+0

Да, это правильно. Я имел в виду. Он делает LEFT OUTER JOIN, который отключает отрицательный эффект наличия нескольких экземпляров одинаковых элементов в .joins. Попробуй! – p1100i

+0

О, интересно. И я раздражен тем, что включает в себя призывы к загрузке в виде отдельного запроса или левого внешнего соединения при других обстоятельствах? –

0

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

Что-то вроде:

class Student < ActiveRecord::Base 
    has_many :gpas, order: "value DESC", conditions: "foo = bar" #<-whatever conditions you want here 
end 

class Gpa < ActiveRecord::Base 
    belongs_to :student 
end 

Использование опций, все, что вам нужно сделать, сделать вызов и пусть Rails сделать большую часть тяжелого подъема.

Если вы получаете в тупике, есть еще несколько вариантов здесь: http://guides.rubyonrails.org/association_basics.html

Просто сделайте поиск по ключевым словам на странице для «The has_and_belongs_to_many ассоциации поддерживает следующие варианты:»

-1

Создать метод last_gpa получить последний GPA создан, а затем выполнить стандарт Ruby sort.

def last_gpa 
    a.gpas.order(:order=>"created_at DESC").first 
end 


Student.all.sort { |a, b| a.last_gpa <=> b.last_gpa } 
+0

Это очень неэффективно, это приведет к извлечению всех учеников, а затем для каждого из них вы получите все gpa. Imho, вы хотели бы использовать возможности базы данных. – nathanvda

0

Это должно сработать для вас. Попробуйте SQL запрос

SELECT * FROM students WHERE id IN 
     (SELECT student_id 
      FROM gpa 
      GROUP BY student_id 
      ORDER BY created_at DESC); 
+0

Это не заказывает учеников. записи возвращаются по идентификатору. –

0

Я думаю, вы могли бы попробовать этот метод:

class Student < ActiveRecord::Base 
    attr_accessible :name 
    has_many :gpas 

    def self.by_last_gpas 
    sql = <<-SQL 
     select students.*, 
     (
      select gpas.created_at from gpas where student_id=students.id 
      order by gpas.created_at desc 
      limit 1 
     ) as last_created_at 
     from students 
     order by last_created_at desc 
    SQL 
    Student.find_by_sql(sql) 
    end 
end 
0

Быстрые и грязные:

Вы можете как-то реализовать запрос, как это для извлечения объектов AR вам необходимо:

select s.* from students s, gpas g 
    where s.id = gpas.student_id 
    and gpas.id in (select max(id) from gpas group by student_id) 
    order by gpas.value 

Предполагая, что идентификатор выше для записей с более высоким created_at.

ИЛИ

Nicer путь:

Я полагаю, вы будете нуждаться в последнем счете GPA студента очень часто. Почему бы не добавить новый столбец :last_gpa_score к студенческой модели?

Вы можете использовать обратный вызов, чтобы сохранить это поле согласованным и автозаполненным, i. e .:

class Gpa < ActiveRecord::Base 
    belongs_to :student 
    after_save :update_student_last_score 

    private 
    def update_student_last_score 
     self.student.update_last_gpa_score 
    end 
end 

class Student < ActiveRecord::Base 
    has_many :gpas 

    def update_last_gpa_score 
    self.last_gpa_score = self.gpas.order("created_at DESC").first.value 
    end 
end 

Тогда вы можете делать все, что хотите, с помощью поля last_gpa_score на ученике.

0

Вам нужно будет настроить имена таблиц и имена столбцов в соответствии с вашей БД.

SELECT s.*, g.* 
    FROM (SELECT studentId, MAX(gpaDate) as gpaDate FROM gpas GROUP BY studentId) maxgpa 
    JOIN gpas g 
    ON g.studentid = maxgpa.studentId 
    AND g.gpaDate = maxgpa.gpaDate 
    JOIN students s 
    ON s.studentId = g.studentId 
ORDER g.gpaDate DESC 
Смежные вопросы