简体   繁体   English

根据Rails中的最后一个has_many记录进行排序

[英]Sort based on last has_many record in Rails

I have a Student model and a Gpa model. 我有Student模型和Gpa模型。 Student has_many Gpa . Student has_many Gpa How would I sort students based on their most recently created gpa record's value attribute? 我如何根据最近创建的gpa记录的value属性对学生进行排序?

NOTE: I don't want to sort an individual student's GPAs based on the created date. 注意:我不想根据创建的日期对单个学生的GPA进行排序。 I would like to pull ALL students and sort them based on their most recent GPA record 我想拉动所有学生并根据他们最近的GPA记录对他们进行排序

class Student < ActiveRecord::Base
  has_many :gpas
end

@students = Student.order(...)

assuming the gpas timestamp is updated_at 假设gpas时间戳是updated_at

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

To include students without gpas 包括没有gpas的学生

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

if you dont like the distinct that uniq creates, you can use some raw sql 如果你不喜欢uniq创建的distinct ,你可以使用一些原始的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')

I'm not sure that there's a way of achieving this in any kind of convenient and mostly-ruby way. 我不确定是否有办法以任何方便和大多数红宝石的方式实现这一点。 The SQL required for an efficient implementation probably requires an order based on join -- something like ... 高效实现所需的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)

I'm not sure if that's legal in MySQL, but if it is you could probably just: 我不确定这在MySQL中是否合法,但如果它是你可能只是:

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

On the other hand, it seems like the last value would be an important one, so you might like to implement a callback on gpas to set a "last_gpa_id" or "last_gpa_value" in the students table to make this common join more efficient. 另一方面,似乎最后一个值是重要的值,因此您可能希望在gpas上实现回调以在学生表中设置“last_gpa_id”或“last_gpa_value”以使此公共连接更有效。

Then of course the implementation would be trivial. 当然,实施将是微不足道的。

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

Still it's important to note that this will include Students, who has got no gpas . 值得注意的是,这将包括没有gpas But you can filter that easly out with @students.delete_if{ |s| s.gpas.blank? } 但你可以使用@students.delete_if{ |s| s.gpas.blank? }轻松过滤掉 @students.delete_if{ |s| s.gpas.blank? }

可能是类似的东西

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

You could try adding some options to the relationships in your models. 您可以尝试为模型中的关系添加一些选项。

Something like: 就像是:

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

Using options, all you have to do make a call and let Rails do most of the heavy lifting. 使用选项,您只需拨打电话,让Rails完成大部分繁重工作。

If you get stumped, there are several more options here: http://guides.rubyonrails.org/association_basics.html 如果你感到难过,还有几个选项: http//guides.rubyonrails.org/association_basics.html

Just do a keyword search on the page for "The has_and_belongs_to_many association supports these options:" 只需在页面上执行关键字搜索“has_and_belongs_to_many关联支持以下选项:”

This should work for you. 这应该适合你。 Try this SQL query 试试这个SQL查询

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

I think, you could try this method: 我想,你可以试试这个方法:

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

Quick and dirty: 又快又脏:

You can somehow implement a query like this to fetch AR objects you need: 你可以以某种方式实现这样的查询来获取你需要的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

Assuming that id is higher for records with higher created_at. 假设对于具有较高created_at的记录,id更高。

OR 要么

Nicer way: 更好的方式:

I assume, you'll need student's last GPA score very often. 我想,你经常需要学生的最后一次GPA分数。 Why not add a new column :last_gpa_score to Student model? 为什么不添加一个新列:last_gpa_score到Student模型?

You can use callback to keep this field consistent and autofilled, ie: 您可以使用回调来保持此字段的一致性和自动填充,即:

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

Then you can do whatever you like with last_gpa_score field on student. 然后你可以用学生的last_gpa_score字段做任何你喜欢的事情。

You'll have to adjust the table names and column names to match your DB. 您必须调整表名和列名以匹配您的数据库。

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

Create a last_gpa method to get the last GPA created and then perform a standard Ruby sort. 创建last_gpa方法以获取最后创建的GPA,然后执行标准Ruby排序。

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


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

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

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