rails按关联子集查找对象

时间:2016-07-21 00:23:05

标签: sql ruby-on-rails

我有两个主要模型ProductResource。产品有许多资源(制造所需的资源),资源可用于生产许多产品,因此我有一个ProductResource`连接表。

class ProductResource < ActiveRecord::Base
  belongs_to :product
  belongs_to :resource
end
class Product < ActiveRecord::Base
  has_many :product_resources
  has_many :resources, :through => :product_resources
end
class Resource < ActiveRecord::Base
  has_many :product_resources
  has_many :products, :through => :product_resources
end

假设我有4个产品(p1,p2,p3和p4)和3个资源(r1,r2&amp; r3),并且通过product_resources它们就像这样关联;

p1.resources #=> [r1]
p2.resources #=> [r1, r2]
p3.resources #=> [r1, r2, r3]
p4.resources #=> [r1, r3]

所以这说p2例如需要资源r1和r2来生成它 我想要的是有一个查询我可以说:如果我有x资源,我可以制作哪些产品。换句话说,我希望能够选择需要全部或子集的给定资源集但不需要任何其他资源的产品。

例如,我可能想要找到给定资源r1和r2的产品;那应该返回p1和p2。或者查询资源r1和r3,它们应该只返回p4。如果我只查询r2那么它不应该返回任何东西,因为没有任何产品只能用r2制作。如果我找到的产品可以用r1,r2和amp; r3,那么它应该返回所有4个产品

我试图这样做的一种方法就是使用Set这样:

available_resources = Set.new(Resource.where(:name => ['r1', 'r2']))
Product.all.select{|product| Set.new(product.resources).subset?(available_resources) }

这产生了预期的结果,但显然是错误的方法,并且非常慢(因为有成千上万的产品和资源)。

我也尝试过这样的方式,首先选择可用的资源,然后为这些资源选择product_resources并返回需要不可用资源的product_ids,最后从中排除那些ID选择产品。

not_available = Resource.where("name not in (?)", ['r1','r2']).pluck(:id)
excluded_product_ids = ProductResource.select(:product_id).where(:resource_id => not_available).distinct.pluck(:product_id)
products = Product.where.not(:id => excluded_product_ids)

这更快,但仍然很慢(> 3000毫秒)。

是否有更好的方法来查找通过HABTM关联找到一组给定关联对象的或子集的对象?

1 个答案:

答案 0 :(得分:0)

有趣的问题,可能不会是一个简单的解决方案。

所以 - 如果它只是一个资源,那么解决方案就很简单了:

my_resource.products

但是因为它是一组&gt; = 1资源,所以不那么容易。 我从这里开始接近它:

product_ids = ProductResource.where(:resource_id => my_resources).pluck(:product_id)
Product.find(product_ids)

以上只是一个开始,因为它会提取所有具有1个但不是所有必需资源的产品,因此您可能需要使用ruby进行优化,例如:

my_resource_ids = my_resources.pluck(:id)
product_ids = ProductResource.where(:resource_id => my_resource_ids).pluck(:product_id)
products = Product.find(product_ids).select{|pr| my_resource_ids.all?{|rid| pr.resource_ids.include?(rid) } }

它有点丑陋,但它会细化产品集,然后将其进一步细化到包含所有资源的产品......

注意:我相信有一种方法可以将所有这些封装到一个SQL查询中 - 这将是最快捷的方式......但我的SQL-fu还不够好现在要做到这一点......但你经常可以逐步接近这样的解决方案。