ActiveRecord
Đối với ActiveRecord, bạn có thể đặt một phương thức như thế này trong lớp Item của mình:
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
Sau đó, bạn có thể lọc các truy vấn của mình như sau:
category_ids = [1,2,3]
Item.where(id: Item.with_all_categories(category_ids))
Bạn cũng có thể sử dụng phạm vi để làm cho nó thân thiện hơn một chút:
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])
Cả hai sẽ tạo ra SQL này
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)
Về mặt kỹ thuật, bạn không cần distinct
một phần của truy vấn con đó, nhưng tôi không chắc liệu có hay không sẽ tốt hơn cho hiệu suất.
SQL
Có một vài cách tiếp cận trong SQL thô
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
)
Điều đó sẽ hoạt động trong SQL Server - cú pháp có thể hơi khác trong Postgres. Hoặc
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)