这是一个棘手的问题,也许有更多 SQL 技能的人可以提供更优化的答案,但这是我想出的可以在数据库中做你想做的事情:
首先,您必须提供用户 A 和用户 B 的 id:
relation = User.where id: [user_a.id, user_b.id]
为此,我们可以将我们的联接链接到组关联:
relation = relation.joins(:groups)
这将返回这两个用户的组,但是,我们希望尽量减少重复:
results = relation.select("DISTINCT(groups.id), users.*, groups.*")
DISTINCT SQL 语句将减少我们的重复项,但并非一直如此,因为当我们在 Ruby 中返回结果时,ActiveRecord 会将每个用户的组分配给每个用户对象,即使两个用户之间的某些组是相同的.
所以,我们 map 和 uniq 他们:
results.flat_map(&:groups).uniq
这给了我们两个用户组的联合。
完整的查询:
User.where(id: [user_a.id, user_b.id]).
joins(:groups).
select("DISTINCT(groups.id), users.*, groups.*").
flat_map(&:groups).
uniq
这是将执行的 SQL 语句:
SELECT DISTINCT(groups.id), users.*, groups.*
FROM "users"
INNER JOIN "groups_memberships"
ON "groups_memberships"."user_id" = "users"."id"
INNER JOIN "groups"
ON "groups"."id" = "groups_memberships"."group_id"
WHERE "users"."id" IN (1, 2)
请注意,此方法的关键是提前知道用户 ID,然后只需加入 groups 表。得到这些结果后,我们只需询问每个用户的位置,然后uniq 他们。
更新
这是处理此问题的相反方法:从组到用户的查询:
Group.joins(groups_memberships: :user).
where(users: { id: [user_a.id, user_b.id] }).
uniq
这将执行这条 SQL 语句:
SELECT DISTINCT "groups".*
FROM "groups"
INNER JOIN "groups_memberships"
ON "groups_memberships"."location_id" = "groups"."id"
INNER JOIN "users"
ON "users"."id" = "groups_memberships"."user_id"
WHERE "users"."id" IN (1, 2)
从某种意义上说,这种方法更优化,因为它在数据库中执行所有操作,并且在内存中没有 uniqing。请注意,在这种情况下,uniq 方法如何将DISTINCT 添加到查询本身并返回相同的结果。
更正
之前的解决方案是返回一个并集而不是一个交集。由于缺乏更纯粹的 SQL 答案,我会留下这个:
results = Group.joins(groups_memberships: :user).
where(users: { id: [user_a.id, user_b.id] }).
uniq
intersection = results.select do |group|
[user_a.id, user_b.id].all? { |id| group.user_ids.include? id }
end
缺点是它会为每个组做一个额外的查询,但至少会让你得到联合的交集,直到你找到一个纯 SQL 解决方案。如果你找到一个我很想看到它!