想要在Rails 3中find没有关联logging的logging

考虑一个简单的关联…

class Person has_many :friends end class Friend belongs_to :person end 

让所有在ARel和/或meta_where中没有朋友的人最干净的方法是什么?

然后呢怎么样has_many:通过版本

 class Person has_many :contacts has_many :friends, :through => :contacts, :uniq => true end class Friend has_many :contacts has_many :people, :through => :contacts, :uniq => true end class Contact belongs_to :friend belongs_to :person end 

我真的不想使用counter_cache – 而我从我读过的东西不能用has_many:通过

我不想拉所有的person.friendslogging,并在Ruby中通过它们循环 – 我想有一个查询/范围,我可以使用meta_searchgem

我不介意查询的性能成本

而离实际的SQL越远越好…

这仍然和SQL非常接近,但是在第一种情况下,它应该让所有没有朋友的人都可以看到:

 Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)') 

更好:

 Person.includes(:friends).where( :friends => { :person_id => nil } ) 

对于这个基本上是一样的事情,你依靠一个没有朋友的人也没有联系的事实:

 Person.includes(:contacts).where( :contacts => { :person_id => nil } ) 

更新

在评论中有一个关于has_one的问题,所以只是更新。 这里的技巧是includes()期望关联的名字,但是where需要表的名字。 对于一个人来说,协会通常会以单数formsexpression,所以变化,但是where()部分保持原样。 所以如果一个Person只有一个Person那么你的陈述就是:

 Person.includes(:contact).where( :contacts => { :person_id => nil } ) 

更新2

有人问起了反面,没有人的朋友。 正如我在下面评论的那样,这实际上让我意识到最后一个字段(上面的:person_id )实际上并不需要与你要返回的模型相关联,它只是连接表中的一个字段。 他们都将是nil所以它可以是任何一个。 这导致了上述更简单的解决scheme:

 Person.includes(:contacts).where( :contacts => { :id => nil } ) 

然后切换这个让没有人的朋友变得更简单,你只改变前面的类:

 Friend.includes(:contacts).where( :contacts => { :id => nil } ) 

更新3 – Rails 5

感谢@Anson的优秀的Rails 5解决scheme(给他一些+ 1的答案,下面),你可以使用left_outer_joins来避免加载关联:

 Person.left_outer_joins(:contacts).where( contacts: { id: nil } ) 

我已经把它包含在这里,所以人们会find它,但他值得这个+ 1。 伟大的加法!

smathy有一个好的Rails 3的答案。

对于Rails 5 ,可以使用left_outer_joins来避免加载关联。

 Person.left_outer_joins(:contacts).where( contacts: { id: nil } ) 

查看api文档 。 它在拉取请求#12071中被引入。

没有朋友的人

 Person.includes(:friends).where("friends.person_id IS NULL") 

或者至less有一个朋友

 Person.includes(:friends).where("friends.person_id IS NOT NULL") 

你可以通过在Friend上设置示波器来与Arel做到这一点

 class Friend belongs_to :person scope :to_somebody, ->{ where arel_table[:person_id].not_eq(nil) } scope :to_nobody, ->{ where arel_table[:person_id].eq(nil) } end 

那么,至less有一个朋友的人:

 Person.includes(:friends).merge(Friend.to_somebody) 

没有朋友的:

 Person.includes(:friends).merge(Friend.to_nobody) 

dmarkow和Unixmonkey的答案都给了我我需要的东西 – 谢谢!

我试了两个在我的真实应用程序,并得到他们的时间 – 这是两个范围:

 class Person has_many :contacts has_many :friends, :through => :contacts, :uniq => true scope :without_friends_v1, -> { where("(select count(*) from contacts where person_id=people.id) = 0") } scope :without_friends_v2, -> { where("id NOT IN (SELECT DISTINCT(person_id) FROM contacts)") } end 

运行一个真正的应用程序 – 约700人“logging的小桌子 – 平均5次运行

Unixmonkey的方法( :without_friends_v1 without_friends_v1)813ms /查询

dmarkow的方法( :without_friends_v2 without_friends_v2)891ms /查询(减慢〜10%)

但是,然后它发生在我身上,我不需要调用DISTINCT()...我正在寻找Personlogging没有Contacts – 所以他们只需要person_ids联系人person_ids列表。 所以我尝试了这个范围:

  scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") } 

得到相同的结果,但平均为425毫秒/电话 – 接近一半的时间…

现在你可能需要DISTINCT在其他类似的查询 – 但对我的情况这似乎工作正常。

谢谢你的帮助

不幸的是,你可能正在研究一个涉及SQL的解决scheme,但你可以在一个范围内设置它,然后使用该范围:

 class Person has_many :contacts has_many :friends, :through => :contacts, :uniq => true scope :without_friends, where("(select count(*) from contacts where person_id=people.id) = 0") end 

然后为了得到它们,你可以做Person.without_friends ,也可以用其他Arel方法链接: Person.without_friends.order("name").limit(10)

一个不存在的相关子查询应该是快速的,特别是当行数和子对父logging的比率增加时。

 scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)") 

另外,要被一个朋友过滤掉,例如:

 Friend.where.not(id: other_friend.friends.pluck(:id))