ActiveRecord:如何找到所有孩子符合条件的父母?
假设我有一个包含许多Child
的Parent
模型,那个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中,这很好。
给定Parent
和Child
,并且child与OtherParent
belongs_to
关系(假定为Rails默认值):
Parent.joins(:childs).where('other_parent_id = ?', other_parent_id)