通过关联记录的 AND 进行多对多关联查询

问题描述 投票:0回答:1

我有一个 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。

ruby-on-rails activerecord
1个回答
0
投票

您可以使用

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
© www.soinside.com 2019 - 2024. All rights reserved.