ActiveRecord:如果其中至少有一条记录不符合条件,则排除组

ActiveRecord: Exclude group if at least one record within it doesn't meet condition

我有两个模型:ownerpet。主人 has_many :pets 和宠物 belongs_to :owner.

我想做的是 只抓住那些拥有 宠物的主人 ,这些宠物的重量都超过 30 磅

#app/models/owner.rb
class Owner < ActiveRecord::Base
  has_many :pets
  #return only those owners that have heavy pets
end

#app/models/pet.rb
class Pet < ActiveRecord::Base
  belongs_to :owner
  scope :heavy, ->{ where(["weight > ?", 30])}
end

这是我的数据库中的内容。我有三个主人:

  1. Neil所有这些都很重
  2. 约翰所有这些都不重
  3. Bob他的一些宠物很重一些不重 .

查询应该 return 只有 Neil。现在我尝试 return NeilBob.

您只需将 uniq 添加到您的范围:

scope :heavy_pets, -> { uniq.joins(:pets).merge(Pet.heavy) }

它在数据库级别上工作,使用 distinct 查询。

您可以为每个owner_id组成一个组并检查,如果组中的所有行都符合要求的条件或至少有一行不符合它,您可以用 group byhaving 子句实现:

scope :heavy, -> { group("owner_id").having(["count(case when weight <= ? then weight end) = 0", 30]) }

还有另一种选择,更多的是 Rails-ActiveRecord 方法:

scope :heavy, -> { where.not(owner_id: Pet.where(["weight <= ?", 30]).distinct.pluck(:owner_id)).distinct }

此处获取所有不符合条件的owner_id矛盾搜索),并将其从原始查询结果中排除。

如果分两步进行,首先得到所有 owner_ids 至少有 1 只重型宠物,然后得到所有 owner_ids 至少有 1 只非重型宠物,然后抓住 id 存在于第一个数组中但不存在于第二个数组中的所有者?

类似于:

scope :not_heavy, -> { where('weight <= ?', 30) }     

...

owner_ids = Pet.heavy.pluck(:owner_id) - Pet.not_heavy.pluck(:owner_id)
owners_with_all_pets_heavy = Owner.where(id: owner_ids)

这不就是找到宠物的最小重量大于某个值的主人吗:

scope :heavy, -> { group("owner_id").joins(:pets).having("min(pets.weight) >= ?", 30)}

或者相反,

scope :light, -> { group("owner_id").joins(:pets).having("max(pets.weight) < ?", 30)}

顺便说一句,这些是主人的范围,不是宠物

另一种方法是将其变成 Owner 的范围:

Owner.where(Pet.where.not("pets.owner_id = owners.id and pets.weight < ?", 30).exists)

略有不同,因为它正在检查是否存在重量小于 30 的宠物,所以如果主人没有宠物,那么此条件将匹配该主人。

在数据库方面,这将是对大型数据集最有效的查询。

对这两种方法都建议为宠物编制索引(owner_id,重量)。