Ruby on rails 如何查找具有*所有*匹配类别的项目
我有两个模型,Item和Category,通过联接表联接。我想查询项以仅查找与类别列表匹配的项。我的模型看起来像:Ruby on rails 如何查找具有*所有*匹配类别的项目,ruby-on-rails,ruby,postgresql,rails-activerecord,Ruby On Rails,Ruby,Postgresql,Rails Activerecord,我有两个模型,Item和Category,通过联接表联接。我想查询项以仅查找与类别列表匹配的项。我的模型看起来像: class Item < ActiveRecord::Base has_and_belongs_to_many :categories end class Category < ActiveRecord::Base has_and_belongs_to_many :items end 我只想找到属于所有3个类别的项目。使用ActiveRecord实现此目的的
class Item < ActiveRecord::Base
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :items
end
我只想找到属于所有3个类别的项目。使用ActiveRecord实现此目的的最佳方法是什么
我需要自己写where条件吗?如果需要,PostgreSQL的正确语法是什么?我尝试过各种风格的“WHERE ALL IN(1,2,3)”,但只会出现语法错误
更新:
根据大家对我的回答,我可以非常接近
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
时,我得到的是散列而不是记录计数:
{189 => 6, 3067 => 6, 406 => 6}
我可以计算结果哈希中的键数以获得真正的记录数,但这是一个非常不雅观的解决方案。这段代码怎么样
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?}
活动记录
对于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)
刚刚尝试了Alex的惊人建议:通过设置,它产生了一个令人惊讶的结果:当我查找具有完全[6,7,8]类别的项目时,它还返回与所有6,7,8类别和更多类别匹配的项目,即具有[6,7,8,9]类别的项目 从技术上讲,基于代码,它是正确的结果,因为having子句要处理where子句的查询结果,因此来自Alex代码的having子句的所有可能计数结果都是1、2或3,但可能不是4或更多 为了克服这种情况,我添加了一个category counter缓存,并在having子句之前预先筛选了category counts,因此它只返回带有[6,7,8]类别的项(没有额外的) 对于预筛选类别计数,我不知道如何在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
class项
这是相同的吗?不,它返回任何类别中的项目,而不是所有类别中的项目。
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
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