考虑一个简单的关联……
class Person
has_many :friends
end
class Friend
belongs_to :person
end
让所有在rel和/或meta_where中没有朋友的人获得朋友的最干净的方法是什么?
然后是has_many:through version
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:through一起工作
我不想拉出所有的person.friends记录并在Ruby中循环它们-我想要有一个可以与meta_search gem一起使用的查询/范围
我不介意查询的性能成本
而且离实际的SQL越远越好……
不幸的是,你可能正在寻找一个涉及SQL的解决方案,但你可以在一个范围内设置它,然后只使用该范围:
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.order("name").limit(10)
来自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个“Person”记录——平均运行5次
Unixmonkey的方法(:without_friends_v1) 813ms / query
Dmarkow的方法(:without_friends_v2) 891ms /查询(~ 10%慢)
但后来我想到,我不需要调用DISTINCT()…我正在寻找没有联系人的人记录-所以他们只需要不在联系人person_ids列表中。所以我尝试了这个范围:
scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }
这得到了相同的结果,但平均425毫秒/次通话——近一半的时间……
现在,您可能需要在其他类似的查询中使用DISTINCT -但对于我的情况,这似乎工作得很好。
谢谢你的帮助
更新4 - Rails 6.1
感谢Tim Park指出,在即将到来的6.1中,你可以这样做:
Person.where.missing(:contacts)
也要感谢他链接到的那个帖子。
更新3 - Rails 5
感谢@Anson为优秀的Rails 5解决方案(给他一些+1他的回答下面),你可以使用left_outer_joins来避免加载关联:
Person.left_outer_joins(:contacts).where(contacts: { id: nil })
我把它放在这里,这样人们就能找到它,但他应该得到+1。伟大的除了!
更新2
有人反问,朋友没有人。正如我在下面评论的那样,这实际上让我意识到,最后一个字段(上面:the:person_id)实际上不必与您返回的模型相关,它只需要是连接表中的一个字段。它们都是nil,所以它可以是它们中的任何一个。这将导致上述问题的一个更简单的解决方案:
Person.includes(:contacts).where(contacts: { id: nil })
然后切换到返回没有人的朋友变得更简单,你只改变前面的类:
Friend.includes(:contacts).where(contacts: { id: nil })
更新
在评论中有一个关于has_one的问题,所以只是更新。这里的技巧是includes()期望关联的名称,而where期望表的名称。对于has_one,关联通常用单数表示,因此会发生变化,但where()部分保持不变。所以如果Person只有_one:contact,那么你的语句将是:
Person.includes(:contact).where(contacts: { person_id: nil })
原始
好:
Person.includes(:friends).where(friends: { person_id: nil })
对于hmt来说,基本上是一样的事情,你依赖的事实是,一个没有朋友的人也没有联系人:
Person.includes(:contacts).where(contacts: { person_id: nil })
没有朋友的人
Person.includes(:friends).where("friends.person_id IS NULL")
或者至少有一个朋友
Person.includes(:friends).where("friends.person_id IS NOT NULL")
你可以通过在Friend上设置作用域来实现这一点
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
还有至少有一个朋友的人:
Person.includes(:friends).merge(Friend.to_somebody)
没有朋友的:
Person.includes(:friends).merge(Friend.to_nobody)