查询两个用户之间的公共组

Query common Groups between two Users

我想查询两个用户之间的公共组。

这是我的模型:

用户模型

class User < ActiveRecord::Base
  has_many :group_memberships
  has_many :groups, through: :group_memberships
end

群模型

class Group < ActiveRecord::Base
  has_many :group_memberships
  has_many :users, through: :group_memberships
end

在用户和组之间加入Table模型

class GroupMembership < ActiveRecord::Base
  belongs_to :user
  belongs_to :group
end

比方说,我有两个用户 A 和 B,我想获得用户 A 和用户 B 共同所属的组。

例如:

用户 A 是群组 [W, X, Y]

的成员

用户 B 是群组 [W, X, Z]

的成员

使用此查询,预期答案将是组 [W, X]

(Groups_From_A∩Groups_From_B)

我已经找到了一些答案,但他们不仅仅使用活动记录:

user_a.groups & user_b.groups

我不想在内存中执行此操作,而是在数据库中执行此操作。

这是一个棘手的问题,也许拥有更多 SQL 技能的人可以提供更优化的答案,但这是我想出的将在数据库中做你想做的事情:

首先,您必须提供用户A和用户B的id:

relation = User.where id: [user_a.id, user_b.id]

为此,我们可以将连接链接到组关联:

relation = relation.joins(:groups)

这将 return 这两个用户的组,但是,我们希望尽量减少重复:

results = relation.select("DISTINCT(groups.id), users.*, groups.*")

DISTINCT SQL 语句将减少我们的重复,但不是所有的方式,因为当我们在 Ruby 中得到结果时,ActiveRecord 会将每个用户的组分配给每个用户对象,即使两个用户之间的某些对象相同。

所以,我们 mapuniq 他们:

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 table。在我们得到这些结果后,我们只需要询问每个用户的位置,然后我们 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)

从某种意义上说,这种方法更优化,因为它在数据库中执行所有操作,而不 uniq 在内存中执行。请注意本例中的 uniq 方法如何将 DISTINCT 添加到查询本身并将 return 相同的结果。

更正

之前的解决方案是return并集而不是交集。由于缺少更纯粹的 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 解决方案。如果你找到一个,我很乐意看到它!

我做到了!! \o/

在 @zerkms 先生的一点帮助下发布了答案 here.

好吧,我知道这很棘手,但结果比我想象的要容易。

最终查询:

Group.joins(:users).where(users: { id: [user_a.id, user_b.id] }).group("groups.id").having("COUNT(users.id)=2")

解释:

我从用户 A 和 B 查询了群组:

Group.joins(:users).where(users: { id: [user_a.id, user_b.id] }).(...)

它给了我一个数组,其中包含来自两个用户的所有组,甚至是重复的。

所以我将它们分组并得到 2 的计数。因为当我们有一个重复的组时,这意味着该组是从用户 A 和用户 B 查询的。

   (...).group("groups.id").having("COUNT(users.id)=2")

它运行良好,但如果您有更好或不同的解决方案,我将很高兴看到。

谢谢你们,也谢谢 @zerkms 先生,我只是 "translated" 你对 RoR 的回答。