查询两个用户之间的公共组
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 会将每个用户的组分配给每个用户对象,即使两个用户之间的某些对象相同。
所以,我们 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
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 的回答。
我想查询两个用户之间的公共组。
这是我的模型:
用户模型
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 会将每个用户的组分配给每个用户对象,即使两个用户之间的某些对象相同。
所以,我们 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
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 的回答。