想要在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()...
我正在寻找Person
logging没有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))