ActiveRecord:如果其中至少有一条记录不符合条件,则排除组
ActiveRecord: Exclude group if at least one record within it doesn't meet condition
我有两个模型: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,所有这些都很重;
- 约翰,所有这些都不重;
- Bob,他的一些宠物很重和一些不重 .
查询应该 return 只有 Neil。现在我尝试 return Neil 和 Bob.
您只需将 uniq
添加到您的范围:
scope :heavy_pets, -> { uniq.joins(:pets).merge(Pet.heavy) }
它在数据库级别上工作,使用 distinct
查询。
您可以为每个owner_id
组成一个组并检查,如果组中的所有行都符合要求的条件或至少有一行不符合它,您可以用 group by
和 having
子句实现:
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,重量)。
我有两个模型: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,所有这些都很重;
- 约翰,所有这些都不重;
- Bob,他的一些宠物很重和一些不重 .
查询应该 return 只有 Neil。现在我尝试 return Neil 和 Bob.
您只需将 uniq
添加到您的范围:
scope :heavy_pets, -> { uniq.joins(:pets).merge(Pet.heavy) }
它在数据库级别上工作,使用 distinct
查询。
您可以为每个owner_id
组成一个组并检查,如果组中的所有行都符合要求的条件或至少有一行不符合它,您可以用 group by
和 having
子句实现:
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,重量)。