ActiveRecord
W przypadku ActiveRecord możesz umieścić taką metodę w swojej klasie 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
Następnie możesz filtrować swoje zapytania w następujący sposób:
category_ids = [1,2,3]
Item.where(id: Item.with_all_categories(category_ids))
Możesz także skorzystać z zakresów, aby uczynić go trochę bardziej przyjaznym:
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])
Oba wytworzą ten 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)
Z technicznego punktu widzenia nie potrzebujesz distinct
część tego podzapytania, ale nie jestem pewien, czy z lub bez byłoby lepsze dla wydajności.
SQL
W surowym SQL jest kilka podejść
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
)
To zadziała w SQL Server - składnia może być nieco inna w Postgresie. Lub
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)