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

时间:2015-06-29 13:49:48

标签: sql ruby-on-rails ruby activerecord rails-activerecord

我有两个模型: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. John 所有这些都不重;
  3. Bob 他的一些宠物很重有些不重
  4. 查询应仅返回 Neil 。现在我的尝试返回 Neil Bob

4 个答案:

答案 0 :(得分:4)

您可以为每个owner_id组成一个组并检查,如果组中的所有行都匹配所需的条件或至少有一行与它不匹配,您可以使用{实现它{1}}和group by条款:

having

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

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

在这里,您将获得所有不符合条件的scope :heavy, -> { where.not(owner_id: Pet.where(["weight <= ?", 30]).distinct.pluck(:owner_id)).distinct } 按矛盾搜索),并将其从原始查询的结果中排除。

答案 1 :(得分:3)

这不仅仅是找到最小宠物体重大于某个值的所有者:

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.where(Pet.where.not("pets.owner_id = owners.id and pets.weight < ?", 30).exists)

完全不同,因为它检查不存在重量小于30的per,所以如果主人没有宠物,那么这个条件将匹配该主人。

在数据库术语中,这将是对大数据集最有效的查询。

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

答案 2 :(得分:1)

您只需在范围中添加uniq

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

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

答案 3 :(得分:1)

如果你分两步完成,首先你获得至少有1个重宠的所有owner_ids,然后获得至少有1个不重宠物的所有owner_ids,然后抓住其中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)