在以下具有关联的读书俱乐部示例中:
class User
has_and_belongs_to_many :clubs
has_and_belongs_to_many :books
end
class Club
has_and_belongs_to_many :users
has_and_belongs_to_many :books
end
class Book
has_and_belongs_to_many :users
has_and_belongs_to_many :clubs
end
具有特定的俱乐部记录:
club = Club.find(params[:id])
我怎么才能找到俱乐部中所有拥有全部书籍的users
?
club.users.where_has_all_books(books)
答案 0 :(得分:1)
在PostgreSQL中,可以通过单个查询完成。 (也许也可以在MySQL中使用,我不确定。)
因此,首先要进行一些基本假设。 3个表:clubs
,users
和books
,每个表都以id
作为主键。 3个连接表books_clubs
,books_users
,clubs_users
,每个表包含一对ID(对于books_clubs
,它是[book_id
,club_id
]),并且这些对在该表中是唯一的。 IMO的条件相当合理。
建立查询:
首先,让我们获取给定俱乐部的书籍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##)
返回俱乐部的书籍排序列表。然后操作员@>检查第一个数组是否包含第二个元素的所有元素(即用户是否拥有俱乐部的所有书籍)。
由于第一次查询只需要执行一次,因此可以将其移到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
;如果仅获取用户ID,则可以将其删除答案 1 :(得分:0)
它似乎可以通过分组和计数来工作。
club.users.joins(:books).where(books: { id: club.books.pluck(:id) }).group('users.id').having('count(*) = ?', club.books.count)
如果有人知道如何在不进行中间查询的情况下运行查询,那将是很好的,我会接受答案。
答案 2 :(得分:0)
看起来您要进行两个查询,一个查询获取所需的所有ID,另一个查询执行WHERE IN。