Ruby on rails 要在Rails中查找没有关联记录的记录吗
考虑一个简单的关联Ruby on rails 要在Rails中查找没有关联记录的记录吗,ruby-on-rails,arel,meta-where,Ruby On Rails,Arel,Meta Where,考虑一个简单的关联 class Person has_many :friends end class Friend belongs_to :person end 让所有在ARel和/或meta_没有朋友的人去哪里最干净的方法是什么 那么一个有很多的版本呢 class Person has_many :contacts has_many :friends, :through => :contacts, :uniq => true end class Fri
class Person
has_many :friends
end
class Friend
belongs_to :person
end
让所有在ARel和/或meta_没有朋友的人去哪里最干净的方法是什么
那么一个有很多的版本呢
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_搜索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\u friends
,你也可以将其与其他Arel方法链接:Person.without\u friends.order(“name”).limit(10)
这仍然非常接近SQL,但在第一种情况下,它应该可以让所有没有朋友的人都得到:
Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)')
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的方法(:没有朋友的情况下v1
)813ms/query
dmarkow的方法(:没有朋友的情况下)891ms/查询(~10%慢)
但后来我突然想到,我不需要调用DISTINCT()…
我正在寻找没有联系人的个人记录,所以他们只需要不在联系人个人ID的列表中。所以我尝试了这个范围:
scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }
这得到了相同的结果,但平均每次通话425毫秒-几乎一半的时间
现在,您可能需要在其他类似查询中使用DISTINCT
,但对于我的情况,这似乎很好
感谢您的帮助更新4-Rails 6.1
感谢您指出,在即将发布的6.1中,您可以做到以下几点:
Person.where.缺失(:联系人)
多亏了他也联系到了
更新3-Rails 5
感谢@Anson提供了出色的Rails 5解决方案(下面的答案请给他一些+1),您可以使用左外连接来避免加载关联:
Person.left_outer_joins(:contacts).where(contacts: { id: nil })
我把它包括在这里,这样人们会找到它,但他应该为此得到+1。添加得太好了
更新2
有人问起相反的问题,没有人的朋友。正如我在下面所评论的,这实际上使我意识到最后一个字段(上面的:person\u id
)实际上不必与您返回的模型相关,它只需要是联接表中的一个字段。它们都将是nil
,因此可以是它们中的任何一个。这就为上述问题提供了一个更简单的解决方案:
Person.includes(:contacts)。其中(contacts:{id:nil})
然后切换此选项以返回无人好友变得更简单,您只需更改前面的类:
Friend.includes(:contacts).where(contacts:{id:nil})
更新
在评论中有一个关于的问题,请更新。这里的技巧是includes()
需要关联的名称,而where
需要表的名称。对于has_one
而言,关联通常用单数表示,因此会发生变化,但where()
部分保持原样。因此,如果一个人
只有有一个:联系人
,那么你的陈述是:
Person.includes(:contact).where(contacts:{Person\u id:nil})
起初的
更好:
Person.includes(:friends).where(friends:{Person\u id:nil})
对于hmt,这基本上是一样的,你依赖于一个没有朋友的人也没有联系人的事实:
Person.includes(:contacts)。其中(contacts:{Person\u id:nil})
一个不存在的相关子查询应该很快,尤其是当行数和子记录与父记录的比率增加时
scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)")
没有朋友的人
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)
斯马蒂有一个很好的答案
对于Rails 5,您可以使用左外连接来避免加载关联
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
看看这本书。它是在pull请求中引入的。另外,要由一个朋友过滤掉,例如:
Friend.where.not(id: other_friend.friends.pluck(:id))
下面是一个使用子查询的选项:
#场景#一人好友
people=Person.where.not(id:Friend.select(:Person\u id))
#场景#2人联系朋友
people=Person.where.not(id:Contact.select(:Person\u id))
上述表达式应生成以下SQL:
——场景#一人好友
选择人*
来自人们
我不在的地方(
选择friends.person\u id
来自朋友
)
--场景#2人联系朋友
选择人*
来自人们
我不在的地方(
选择contacts.person\u id
来自联系人
)
您可以将其合并到一个更干净的范围中。更好的答案是,不确定为什么另一个被评为可接受。是的,假设您的有一个关联的单数名称,您需要在includes
调用中更改关联的名称。因此,假设它是在Person
中有一个:contact
那么你的代码就是Person.includes(:contact)。其中(:contacts=>{:Person\u id=>nil})
如果你在你的朋友模型中使用一个自定义表名(self.table\u name=“custom\u friends\u table\u name”
),那么就使用Person.includes(:friends)。其中(:custom_friends_table_name=>{:id=>nil})
@smat