cra*_*com 171 ruby-on-rails arel meta-where
考虑一个简单的关联......
class Person
has_many :friends
end
class Friend
belongs_to :person
end
Run Code Online (Sandbox Code Playgroud)
让所有在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
Run Code Online (Sandbox Code Playgroud)
我真的不想使用counter_cache - 而且我从我读过的内容中看起来并不适用于has_many:通过
我不想拉出所有的person.friends记录并在Ruby中循环它们 - 我希望有一个可以与meta_search gem一起使用的查询/范围
我不介意查询的性能成本
离实际的SQL越远越好......
sma*_*thy 411
更好:
Person.includes(:friends).where( :friends => { :person_id => nil } )
Run Code Online (Sandbox Code Playgroud)
对于hmt它基本上是一样的,你依赖的事实是没有朋友的人也没有联系人:
Person.includes(:contacts).where( :contacts => { :person_id => nil } )
Run Code Online (Sandbox Code Playgroud)
has_one在评论中有一个问题,所以只是更新.这里的技巧是includes()期望关联的名称,但where期望表的名称.对于一个has_one关联通常会以单数表示,以便更改,但该where()部分保持不变.所以如果Person只有has_one :contact你的陈述是:
Person.includes(:contact).where( :contacts => { :person_id => nil } )
Run Code Online (Sandbox Code Playgroud)
有人询问反向,没有人的朋友.正如我在下面评论的那样,这实际上让我意识到最后一个字段(上面的:person_id:)实际上并不需要与你返回的模型相关,它只需要是连接表中的一个字段.他们都将是nil如此,它可以是任何一个.这导致了上述更简单的解决方案:
Person.includes(:contacts).where( :contacts => { :id => nil } )
Run Code Online (Sandbox Code Playgroud)
然后切换这个以返回没有人的朋友变得更简单,你只改变前面的班级:
Friend.includes(:contacts).where( :contacts => { :id => nil } )
Run Code Online (Sandbox Code Playgroud)
感谢@Anson提供优秀的Rails 5解决方案(下面给出他的答案为+ 1),您可以使用left_outer_joins以避免加载关联:
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
Run Code Online (Sandbox Code Playgroud)
我把它包括在这里,所以人们会找到它,但他应该得到+ 1s.太棒了!
Ans*_*son 154
smathy有一个很好的Rails 3答案.
对于Rails 5,您可以使用left_outer_joins以避免加载关联.
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
Run Code Online (Sandbox Code Playgroud)
Uni*_*key 102
这仍然非常接近SQL,但它应该让所有人在第一种情况下没有朋友:
Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)')
Run Code Online (Sandbox Code Playgroud)
nov*_*ilo 13
没有朋友的人
Person.includes(:friends).where("friends.person_id IS NULL")
Run Code Online (Sandbox Code Playgroud)
或者至少有一个朋友
Person.includes(:friends).where("friends.person_id IS NOT NULL")
Run Code Online (Sandbox Code Playgroud)
您可以通过设置范围来使用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
Run Code Online (Sandbox Code Playgroud)
然后,至少有一个朋友的人:
Person.includes(:friends).merge(Friend.to_somebody)
Run Code Online (Sandbox Code Playgroud)
无朋友:
Person.includes(:friends).merge(Friend.to_nobody)
Run Code Online (Sandbox Code Playgroud)
cra*_*com 12
来自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
Run Code Online (Sandbox Code Playgroud)
这是一个真正的应用程序 - 小表与约700'人'记录 - 平均5次运行
Unixmonkey的方法(:without_friends_v1)813ms /查询
dmarkow的方法(:without_friends_v2)891ms /查询(慢〜10%)
但后来我发现我不需要呼叫DISTINCT()...我正在寻找没有的Person记录Contacts- 所以他们只需要成为NOT IN联系人列表person_ids.所以我尝试了这个范围:
scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }
Run Code Online (Sandbox Code Playgroud)
这得到了相同的结果,但平均为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
Run Code Online (Sandbox Code Playgroud)
然后为了得到它们,你可以做到Person.without_friends,你也可以用其他Arel方法链接它:Person.without_friends.order("name").limit(10)
| 归档时间: |
|
| 查看次数: |
64965 次 |
| 最近记录: |