我有两个模型:owner
和pet
。所有者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
以下是我的数据库中的内容。我有三个所有者:
查询应仅返回 Neil 。现在我的尝试返回 Neil 和 Bob 。
答案 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)