考虑一个简单的关联……

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)

其他回答

不幸的是,你可能正在寻找一个涉及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)

这仍然非常接近SQL,但它应该在第一种情况下得到所有没有朋友的人:

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

下面是一个使用子查询的选项:

# Scenario #1 - person <-> friend
people = Person.where.not(id: Friend.select(:person_id))

# Scenario #2 - person <-> contact <-> friend
people = Person.where.not(id: Contact.select(:person_id))

上述表达式应该生成以下SQL语句:

-- Scenario #1 - person <-> friend
SELECT people.*
FROM people 
WHERE people.id NOT IN (
  SELECT friends.person_id
  FROM friends
)

-- Scenario #2 - person <-> contact <-> friend
SELECT people.*
FROM people 
WHERE people.id NOT IN (
  SELECT contacts.person_id
  FROM contacts
)

没有朋友的人

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)

来自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 -但对于我的情况,这似乎工作得很好。

谢谢你的帮助