Существует два распространенных решения в SQL для выполнения того, что вы описываете.
автообъединение:
SELECT ...
FROM Projects p
JOIN Categories c1 ON c1.project_id = p.id
JOIN Categories c3 ON c3.project_id = p.id
JOIN Categories c4 ON c4.project_id = p.id
WHERE (c1.id, c3.id, c4.id) = (1, 3, 4);
Примечание. Я использую синтаксис для сравнения кортежей. Это эквивалентно:
WHERE c1.id = 1 AND c3.id = 3 AND c4.id = 4;
В общем, решение для самостоятельного соединения имеет очень хорошую производительность, если у вас есть индекс покрытия. Возможно, Categories.(project_id,id)
будет правильным индексом, но для уверенности проанализируйте SQL с помощью EXPLAIN.
Недостаток этого метода в том, что вам нужно четыре объединения, если вы ищете проекты, которые соответствуют четырем различным категориям. Пять объединений для пяти категорий и т. Д.
Группа-на:
SELECT ...
FROM Projects p
JOIN Categories cc ON c.project_id = p.id
WHERE c.id IN (1, 3, 4)
GROUP BY p.id
HAVING COUNT(*) = 3;
Если вы используете MySQL (я так полагаю), большинство запросов GROUP BY вызывают временную таблицу, и это снижает производительность.
Я оставлю вам упражнение по адаптации одного из этих SQL-решений к эквивалентному Rails ActiveRecord API.