В PostgreSQL это можно сделать одним запросом.(Может быть, и в MySQL, я просто не уверен.)
Итак, сначала несколько основных предположений.3 таблицы: clubs
, users
и books
, каждая таблица имеет id
в качестве первичного ключа.3 таблицы соединения, books_clubs
, books_users
, clubs_users
, каждая таблица содержит пары идентификаторов (для books_clubs
это будет [book_id
, club_id
]), и эти пары уникальны в этой таблице.Вполне разумные условия ИМО.
Построение запроса:
Сначала давайте получим идентификаторы книг из данного клуба:
SELECT book_id
FROM books_clubs
WHERE club_id = 1
ORDER BY book_id
Затем получите пользователей из данного клуба и сгруппируйте их по user.id
:
SELECT CU.user_id
FROM clubs_users CU
JOIN users U ON U.id = CU.user_id
JOIN books_users BU ON BU.user_id = CU.user_id
WHERE CU.club_id = 1
GROUP BY CU.user_id
Объедините эти два запроса, добавив having
ко второму запросу:
HAVING array_agg(BU.book_id ORDER BY BU.book_id) @> ARRAY(##1##)
, где ##1##
- первый запрос.
Что здесь происходит: функция array_agg
из левой части создает отсортированный список (типа array
) book_ids
.Это книги пользователя.ARRAY(##1##)
из правой части возвращает отсортированный список книг клуба.И оператор @> проверяет, содержит ли 1-й массив все элементы 2-го (т.е. если у пользователя есть все книги клуба).
Поскольку 1-й запрос должен бытьвыполняется только один раз, его можно переместить в предложение WITH
.
Ваш полный запрос:
WITH club_book_ids AS (
SELECT book_id
FROM books_clubs
WHERE club_id = :club_id
ORDER BY book_id
)
SELECT CU.user_id
FROM clubs_users CU
JOIN users U ON U.id = CU.user_id
JOIN books_users BU ON BU.user_id = CU.user_id
WHERE CU.club_id = :club_id
GROUP BY CU.user_id
HAVING array_agg(BU.book_id ORDER BY BU.book_id) @> ARRAY(SELECT * FROM club_book_ids);
Его можно проверить в этой песочнице: https://www.db -fiddle.com / f / cdPtRfT2uSGp4DSDywST92 / 5
Оберните его в find_by_sql
и все.
Некоторые примечания:
- заказ по
book_id
не требуется;Оператор @>
работает и с неупорядоченными массивами.У меня просто есть подозрение, что сравнение упорядоченного массива происходит быстрее. JOIN users U ON U.id = CU.user_id
во втором запросе необходимо только для извлечения пользовательских свойств;в случае извлечения только идентификатора пользователя его можно удалить