ActiveRecord:如何找到所有孩子符合条件的父母?

假设我有一个包含许多ChildParent模型,那个Child也属于OtherParent

我怎样才能找到所有其Child所有Child属于任何其他OtherParent

在纯SQL中我能做到

 Parent.find_by_sql(<<SQL) SELECT * FROM parents p WHERE NOT EXISTS ( SELECT * FROM children WHERE parent_id = p.id AND other_parent_id IS NULL ) SQL 

(从这里开始 ),但如果可能的话,我更愿意利用ActiveRecord。

谢谢!


我正在使用Rails 4.2.1和PostgreSQL 9.3

使用arel可以让你相当远。 棘手的部分是如何使用arel自己的查询语法编写整个查询?

这里有一个技巧:在使用where构建查询时,如果使用arel条件,则可以免费获得一些额外的方法。 例如,你可以使用.exists.not你在那里的子查询,它会得到一个(NOT ( EXISTS (subquery)))扔进父级的-clause并且你被设置。

问题是,您如何参考所涉及的表格? 你需要Arel。 你可以使用像a.eq b这样丑陋条件的Arel’s。 但为什么? 由于它是一个相等的条件,你可以使用Rails的条件! 您可以使用散列键引用要查询的表,但对于另一个表(在外部查询中),您可以使用其arel_table 。 看这个:

 parents = Parent.arel_table Parent.where( Child.where(other_parent_id: nil, parent_id: parents[:id]).exists.not ) 

您甚至可以通过稍微使用字符串来减少Arel的使用,并依赖于您可以将子查询作为参数提供给Rails的where 。 它没有多大用处,但它并没有强迫你过多地深入研究Arel的方法,所以你可以使用那个技巧或其他带有子查询的SQL运算符(还有其他的吗?):

 parents = Parent.arel_table Parent.where('NOT EXISTS (?)', Child.where(parent_id: parents[:id], other_parent_id: nil) ) 

这里的两个要点是:

  • 您可以像构建常规查询一样构建子查询,使用Arel引用外部查询的表。 它甚至可能不是真正的表,它可能是别名! 疯狂的事情。
  • 您可以使用子查询作为Rails的参数, where方法很好。

使用特殊的scattle ,您可以将任意SQL转换为ruby(ActiveRecord和ARel查询)

从该工具,您的查询转换为

 Parent.select(Arel.star).where( Child.select(Arel.star).where( Child.arel_table[:parent_id].eq(Parent.arel_table[:id]).and(Child.arel_table[:other_parent_id].eq(nil)) ).ast ) 

拆分查询 –

  • Parent.select(Arel.star)将查询Parent表中的所有列。
  • Child.arel_table带您进入Arel-world,让您在从ruby生成查询时获得更多function。 具体来说, Child.arel_table[:parent_id]为您提供了一个Arel :: Attributes :: Attribute的句柄,您可以在构建查询时继续使用它。
  • .eq.and方法完全符合您的期望,让您构建任意深度和复杂性的查询。

不一定“更干净”,但完全在ruby中,这很好。

给定ParentChild ,并且child与OtherParent belongs_to关系(假定为Rails默认值):

Parent.joins(:childs).where('other_parent_id = ?', other_parent_id)