ActiveRecord
Für ActiveRecord könnten Sie eine Methode wie diese in Ihre Item-Klasse einfügen:
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
Dann können Sie Ihre Abfragen wie folgt filtern:
category_ids = [1,2,3]
Item.where(id: Item.with_all_categories(category_ids))
Sie könnten auch Bereiche verwenden, um es ein wenig freundlicher zu gestalten:
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])
Beide erzeugen dieses 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)
Technisch gesehen brauchen Sie distinct
nicht Teil dieser Unterabfrage, aber ich bin mir nicht sicher, ob mit oder ohne besser für die Leistung wäre.
SQL
Es gibt ein paar Ansätze in rohem 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
)
Das funktioniert in SQL Server - die Syntax könnte in Postgres etwas anders sein. Oder
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)