考虑简单的关联...

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:通过

我不想拉所有的人。Friends记录并在Ruby中循环通过它们 - 我想拥有一个可以与meta_search Gem一起使用的查询/范围

我不介意查询的性能成本

而且离实际SQL越远,越好...

有帮助吗?

解决方案

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

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

其他提示

更好的:

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

对于HMT,基本上是同一件事,您依靠一个没有朋友的人也没有联系的事实:

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

更新

有一个问题 has_one 在评论中,因此只需更新。这里的诀窍是 includes() 期望协会的名称,但 where 期待桌子的名称。为一个 has_one 该协会通常会在单数中表达,以便改变,但是 where() 部分保持原样。因此, Person 只要 has_one :contact 那么您的陈述将是:

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

更新2

有人询问没有人的逆朋友。正如我在下面评论的那样,这实际上使我意识到最后一个领域(上面: :person_id)实际上不必与您要返回的模型相关,它必须是联接表中的字段。他们都会成为 nil 因此,可以是他们中的任何一个。这导致了上述简单的解决方案:

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

然后将其切换为没有人会更简单地返回朋友,您只会在前面更改班级:

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

更新3-铁轨5

感谢@Anson提供出色的Rails 5解决方案(在下面给他一些 +1的答案),您可以使用 left_outer_joins 避免加载关联:

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

我在这里包括了它,所以人们会找到它,但他应该得到 +1。很棒!

Smathy有一个很好的Rails 3答案。

对于铁轨5, , 您可以使用 left_outer_joins 避免加载关联。

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

查看 API文档. 。它是在拉的请求中引入的 #12071.

没有朋友的人

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

或至少有一个朋友

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

您可以通过在AREL上设置范围来完成此操作 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“人”记录的小表格 - 平均5次运行

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

dmarkow的方法(:without_friends_v2)891ms / Query(慢〜10%)

但是后来我想到我不需要打电话 DISTINCT()... 我在找 Person 记录没有 Contacts - 所以他们只需要 NOT IN 联系人列表 person_ids. 。所以我尝试了这个范围:

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

结果得到相同的结果,但平均为425毫秒/呼叫 - 几乎一半时间...

现在您可能需要 DISTINCT 在其他类似的查询中 - 但就我而言,这似乎很好。

谢谢你的帮助

不幸的是,您可能正在寻找涉及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, ,您还可以使用其他AREL方法链接它: Person.without_friends.order("name").limit(10)

不存在相关的子查询应该很快,尤其是随着孩子与父母记录的比率增加。

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))
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top