如何找到* all *匹配类别的项目

我有两个模型,Item和Category,由连接表连接。 我想查询Item以仅查找与类别列表匹配的项目。 我的模型看起来像:

class Item < ActiveRecord::Base has_and_belongs_to_many :categories end class Category < ActiveRecord::Base has_and_belongs_to_many :items end 

我可以轻松找到与任何类别列表匹配的项目。 以下将返回属于类别1,2或3的项目。

 Item.includes(:categories).where(categories: {id:[1,2,3]}) 

我只想找到属于所有3个类别的项目。 使用ActiveRecord实现此目的的最佳方法是什么?

我是否需要回退自己编写where条件,如果是这样,PostgreSQL的正确语法是什么? 我尝试了各种各样的“WHERE ALL IN(1,2,3)”,但只是语法错误。

更新:

基于查找产品匹配所有类别(Rails 3.1)的接受答案,我可以非常接近。

 category_ids = [7,10,12,13,52,1162] Item.joins(:categories). where(categories: {id: category_ids}). group('items.id'). having("count(categories_items.category_id) = #{category_ids.size}") 

不幸的是,当链接.count.size我得到一个Hash而不是记录计数:

 {189 => 6, 3067 => 6, 406 => 6} 

我可以计算结果哈希中的键来获得真实的记录数,但这是一个非常不优雅的解决方案。

ActiveRecord的

对于ActiveRecord,您可以在Item类中放置这样的方法:

 def self.with_all_categories(category_ids) select(:id).distinct. joins(:categories). where('categories.id' => category_ids). group(:id). having('count(categories.id) = ?', category_ids.length) end 

然后您可以像这样过滤您的查询:

 category_ids = [1,2,3] Item.where(id: Item.with_all_categories(category_ids)) 

您还可以使用范围使其更友好:

 class Item scope :with_all_categories, ->(category_ids) { where(id: Item.ids_with_all_categories(category_ids)) } def self.ids_with_all_categories(category_ids) select(:id).distinct. joins(:categories). where('categories.id' => category_ids). group(:id). having('count(categories.id) = ?', category_ids.length) end end Item.with_all_categories([1,2,3]) 

两者都会产生这个SQL

 SELECT "items".* FROM "items" WHERE "items"."id" IN (SELECT DISTINCT "items"."id" FROM "items" INNER JOIN "categories_items" ON "categories_items"."item_id" = "items"."id" INNER JOIN "categories" ON "categories"."id" = "categories_items"."category_id" WHERE "categories"."id" IN (1, 2, 3) GROUP BY "items"."id" HAVING count(categories.id) = 3) 

从技术上讲,您不需要该子查询的distinct部分,但我不确定无论是否有更好的性能。

SQL

原始SQL中有几种方法

 SELECT * FROM items WHERE items.id IN ( SELECT item_id FROM categories_items WHERE category_id IN (1,2,3) GROUP BY item_id HAVING COUNT(category_id) = 3 ) 

这将在SQL Server中工作 – Postgres中的语法可能略有不同。 要么

 SELECT * FROM items WHERE items.id IN (SELECT item_id FROM categories_items WHERE category_id = 1) AND items.id IN (SELECT item_id FROM categories_items WHERE category_id = 2) AND items.id IN (SELECT item_id FROM categories_items WHERE category_id = 3) 

这段代码怎么样

 Item.all.joins(:categories).where(categories: { id: [1, 2, 3] }) 

SQL是

 SELECT "items" . * FROM "items" INNER JOIN "categories_items" ON "categories_items" . "item_id" = "items" . "id" INNER JOIN "categories" ON "categories" . "id" = "categories_items" . "category_id" WHERE "categories" . "id" IN ( 1 ,2 ,3 ) 

我不能肯定地说,但这可能会奏效

 categories = Category.find(1,2,3) items = Item.includes(:categories) items.select{|item| (categories-item.categories).blank?} 

要不就

 Item.all.select{|item| (Category.find(1,2,3)-item.categories).blank?} 

刚尝试了Alex对于has_many的惊人建议:通过设置,它产生了一个令人惊讶的结果:当我查找具有完全[6,7,8]类别的项目时,它还返回与所有6,7,8类别相匹配的项目,即。 [6,7,8,9]类别的项目。

从技术上讲,它是基于代码的正确结果,因为having子句用于处理where子句的查询结果,因此来自Alex代码的having子句的所有可能的计数结果将是1或2或3,但可能不是4或更多。

为了克服这种情况,我添加了一个类别计数器缓存,并在having子句之前预先筛选了类别计数,因此它只返回带有[6,7,8]类别(没有额外)的项目。

  def self.with_exact_categories(category_ids) self. joins(:categories). where('categories.id': category_ids). where('items.categories_count = ?', category_ids.length). group('items.id'). having('count(categories.id) = ?', category_ids.length) end 

对于预筛选类别计数,我不知道如何在where子句中使用聚合函数,但仍然很高兴得知计数器缓存仍然在Rails 4.21中工作。 这是我的模型设置:

 class Item < ActiveRecord::Base has_many :categories_items has_many :categories, through: :categories_items end class CategoriesItem < ActiveRecord::Base belongs_to :category belongs_to :item, counter_cache: :categories_count end class Category < ActiveRecord::Base has_many :categories_items, dependent: :destroy has_many :items, through: :categories_items, dependent: :destroy end class AddCategoriesCountToItems < ActiveRecord::Migration def change add_column :items, :categories_count, :integer, default: 0 end end