15

我有一个Student模型和一个Gpa模型。Studenthas_many Gpa。我将如何根据学生最近创建的 gpa 记录value属性对学生进行排序?

注意:我不想根据创建日期对单个学生的 GPA 进行排序。我想拉所有学生并根据他们最近的 GPA 记录对他们进行排序

class Student < ActiveRecord::Base
  has_many :gpas
end

@students = Student.order(...)
4

10 回答 10

13

假设 gpas 时间戳是updated_at

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

包括没有 gpas 的学生

#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')
于 2013-09-05T15:46:50.323 回答
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”,以使这种常见的连接更有效。

那么当然实现将是微不足道的。

于 2013-06-18T21:24:51.347 回答
1

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

仍然需要注意的是,这将包括没有gpas. 但是你可以很容易地过滤掉@students.delete_if{ |s| s.gpas.blank? }

于 2013-06-18T21:53:16.027 回答
0

大概是这样的

Student.joins(:gpas).order("gpas.value DESC")
于 2013-06-18T16:54:57.067 回答
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

只需在页面上进行关键字搜索“has_and_belongs_to_many 关联支持这些选项:”

于 2013-06-20T00:42:54.590 回答
0

这应该适合你。试试这个 SQL 查询

SELECT * FROM students WHERE id IN 
        (SELECT student_id 
            FROM gpa 
            GROUP BY student_id 
            ORDER BY created_at DESC);
于 2013-09-06T07:16:27.070 回答
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
于 2013-09-11T04:01:14.517 回答
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 较高的记录的 id 较高。

或者

更好的方式:

我想,你会经常需要学生的最后 GPA 分数。为什么不:last_gpa_score向学生模型添加一个新列?

您可以使用回调来保持该字段一致和自动填充,即:

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 字段做任何你喜欢的事情。

于 2013-09-11T11:15:06.390 回答
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
于 2013-09-11T16:29:38.830 回答
-2

创建一个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 }
于 2013-09-05T17:55:52.387 回答