我有一个 Rails 项目,其中包含产品模型和功能模型以及它们之间的多对多关系,如下所示:
# == Schema Information
#
# Table name: products
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
#
class Product < ApplicationRecord
has_many :product_features, dependent: :destroy
has_many :features, through: :product_features
end
# == Schema Information
#
# Table name: features
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
#
class Feature < ApplicationRecord
has_many :product_features, dependent: :destroy
has_many :products, through: :product_features
end
# == Schema Information
#
# Table name: product_features
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# product_id :bigint not null
# feature_id :bigint not null
#
# Indexes
#
# index_product_features_on_product_id (product_id)
# index_product_features_on_feature_id (feature_id)
#
# Foreign Keys
#
# fk_rails_... (product_id => products.id)
# fk_rails_... (feature_id => features.id)
#
class ProductFeature < ApplicationRecord
belongs_to :product
belongs_to :feature
end
我想对产品进行过滤,以返回具有所有功能列表的产品。
例如:
Product 1
有 Feature 1
、Feature 2
和 Feature 3
。
Product 2
有 Feature 2
、Feature 3
和 Feature 4
。
Product 3
有 Feature 3
、Feature 4
和 Feature 5
。
Product 4
有 Feature 2
和 Feature 5
。
如果给定过滤器
Feature 2
和 Feature 3
,它应该返回 Product 1
和 Product 2
,而不是 Product 3
或 Product 4
。
到目前为止我想出的最好的方法如下:
def filter_by_features(feature_ids_array)
product_id_arrays = []
feature_ids_array.each do |feature_id|
product_id_arrays << ProductFeature.where(feature_id: feature_id).pluck(:product_id)
end
Product.where(id: product_id_arrays.inject(:&))
end
我不喜欢这个解决方案,因为它会导致 N+1 次查询。如何重构过滤器以消除 N+1 查询? 该项目基于 Rails 6.0 和 PostGres 12。
您可以使用
joins
和 having
的组合来完成您所需要的。
Product.joins(:features).where(features: {id: feature_ids_array}).group(:id).having('count(*) = ?', feature_ids_array.length)
为了使其正常工作,保证
product_id
模型中 feature_id
和 ProductFeature
组合的唯一性非常重要。最好在数据库级别上通过添加唯一索引来完成此操作:
add_index :product_features, [:product_id, :feature_id], unique: true