想要找到没有导轨中没有相关记录的记录
-
24-10-2019 - |
题
考虑简单的关联...
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。很棒!
没有朋友的人
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))