【问题标题】:Query common Groups between two Users查询两个用户之间的公共组
【发布时间】:2015-04-30 20:19:44
【问题描述】:

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

这是我的模型:

用户模型

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

用户和组之间的联接表模型

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

假设我有两个用户 A 和 B,我想获取用户 A 和用户 B 是共同成员的组。

例如:

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

的成员

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

的成员

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

(Groups_From_A ∩ Groups_From_B)

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

user_a.groups & user_b.groups

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

【问题讨论】:

    标签: mysql ruby-on-rails rails-activerecord


    【解决方案1】:

    我做到了!! \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 先生,我刚刚“翻译”了你对 RoR 的回答。

    【讨论】:

      【解决方案2】:

      这是一个棘手的问题,也许有更多 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 会将每个用户的组分配给每个用户对象,即使两个用户之间的某些组是相同的.

      所以,我们 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 表。得到这些结果后,我们只需询问每个用户的位置,然后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 解决方案。如果你找到一个我很想看到它!

      【讨论】:

      • Distinct 不是函数
      • 更正了我的措辞。谢谢。
      • 但是你仍然在假装它是一个函数。括号没有意义(而且有点误导)。不过最后一点很好。
      • 谢谢,不过我会留下括号,因为查询仍然可以正常工作,而且这个问题不直接与 SQL 语法有关。
      • 嗨@diego.greyrobot,感谢您的回答!我已经测试过了,但这不是我想要的。通过这种方式,我得到了用户 A 和用户 B 的组之间的 UNION,实际上我想要它们之间的 INTERSECTION。在我的问题示例中,查询的正确响应是 [W, X],如果我使用您的答案,它将是 [W, X, Y, Z]。
      猜你喜欢
      • 2015-07-20
      • 1970-01-01
      • 1970-01-01
      • 2020-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-06
      • 1970-01-01
      相关资源
      最近更新 更多