Rails ActiveRecord 加入到作用域中最近的子项



我目前正在尝试编写一个必须检查最近孩子的状态并据此排队的工人。例如,查找年龄最大的孩子在上学的父母

class Parent
  has_many :children
end
class Child
  belongs_to :parent
  lookup_for :status, symbolize: true
end

目前我的范围是:

Parent.joins(:children).
      where("children.birth_date =
            (SELECT MAX(children.birth_date)
             FROM children
             WHERE children.parent_id = parents.parent_id").
      where('children.status = ?', Status[:in_school]').pluck(:parent_id)

似乎应该有更好的方法来做到这一点。 任何想法

澄清一下,我正在寻找所有最大的孩子仍在上学的父母

如果您在

查询中大量使用第一个/或最后一个子项,我会将它们添加为父级中的字段

class Parent
  belongs_to :youngest_child, class_name: Child, inverse_of: :parent
end

好吧,您可以将一些逻辑推送到 joins() 语句中,如下所示,但我看不出如何在某处必须有一个子查询。

Parent.joins(%Q{left join children on children.parent_id = parents.id
                  and children.birth_date = (select max(birth_date) from children 
                  where children.parent_id = parents.id)}).
                where('children.status = ?', Status[:in_school]')

那会是这样的。呵呵

或者,可以使用 order 和 last 查找最后一个子项,然后检查内存中子项的状态。这种方法最多只能得到一个孩子,因此性能是合理的。如下所示:

child=Child.order(:birth_date).joins(:parent).last
parent.do_stuff if child.in_school?

您的查询有效 - 它完成了工作,并且很清楚您要做什么。我在我现在正在处理的应用程序中测试了您的模式,ActiveRecord正确地将它们全部合并到一个查询中,花了 1.4 毫秒

如果要优化性能,可以使用联接来获取最年长的子项,而不是子查询:

Parent.joins("INNER JOIN (
                SELECT c1.*
                FROM children c1
                LEFT JOIN children c2 ON (c1.parent_id = c2.parent_id AND c1.birthdate > c2.birthdate)
                WHERE c2.parent_id IS NULL
              ) c ON parent.parent_id = c.parent_id")
              .where('children.status = ?', Status[:in_school])
              .pluck(:parent_id)
不太

清楚你要做什么,因为它使用一种手动的外部连接,但它也允许嵌套连接使用索引。在与上述相同的测试场景中,这在 0.9 毫秒内执行了相同的查询(几乎是两倍快)。那是在一个非常小的数据库中,只有几百条记录。对于数百万条记录,差异将是显而易见的。

感谢这个 堆栈溢出 答案,用于连接模式。

最新更新