使用 activerecord 发出请求以仅从组中获取用户,而不从其他人中获取

Posted

技术标签:

【中文标题】使用 activerecord 发出请求以仅从组中获取用户,而不从其他人中获取【英文标题】:Make a request with activerecord to get only the users from groups and not from others 【发布时间】:2015-08-13 14:51:27 【问题描述】:

我正在尝试从几个组(具有给定 ID)中获取用户,并从其他组中排除用户。

我尝试过类似的方法:

User.joins(:groups).where(groups: id: ["8939","8950"]).where.not(groups: id: 8942).map(&:id)
  User Load (0.9ms)  SELECT "users".* FROM "users" INNER JOIN "groups_users" ON "groups_users"."user_id" = "users"."id" INNER JOIN "groups" ON "groups"."id" = "groups_users"."group_id" WHERE "groups"."id" IN (8939, 8950) AND "groups"."id" != $1  [["id", 8942]]
=> [119491, 119489, 119490, 119492, 119488, 119484, 119483, 119491, 119482]

但这是不对的

8942中的用户。

Group.find(8942).users.pluck(:id)
  Group Load (0.4ms)  SELECT  "groups".* FROM "groups" WHERE "groups"."id" = $1 LIMIT 1  [["id", 8942]]
   (0.6ms)  SELECT "users"."id" FROM "users" INNER JOIN "groups_users" ON "users"."id" = "groups_users"."user_id" WHERE "groups_users"."group_id" = $1  [["group_id", 8942]]
=> [119490, 119492, 119491, 119457, 119423]

where.not 不适用于用户 "groups"."id" != $1 [["id", 8942]]。为什么?

【问题讨论】:

【参考方案1】:

执行此类操作的正确方法是使用 SQL EXISTS 条件。我希望有一个特定的 ActiveRecord 辅助方法,但目前还没有。

嗯,使用纯 SQL 就好了:

User.where("EXISTS (SELECT 1 FROM groups_users WHERE groups_users.user_id = users.id AND groups_users.group_id IN (?))", [8939, 8950]).
  where("NOT EXISTS (SELECT 1 FROM groups_users WHERE groups_users.user_id = users.id AND groups_users.group_id IN (?))", [8942])

您对原始查询所做的操作是要求不加入 ID 为[8942] 的组到您的查询中,而仅加入 ID 为[8939, 8950] 的组。好吧,您现在可以看到这没有任何意义:这就像要求选择名称为bob 而不是charlie 的每个用户。第二个条件不会向第一个条件添加任何内容。

加入查询是乘以列,所以如果您的用户在每个组中,结果集将是:

user_id | group_id
1       | 8939
1       | 8950
1       | 8942

然后你过滤掉后一行:1 | 8942。尽管如此,用户1 在结果集中并被返回。

并要求数据库只返回不与另一个关系连接的记录,您应该明确使用为此目的而明确存在的NOT EXISTS :)

【讨论】:

再次感谢 EugZol。你是我的NOT EXISTS 支持者。我肯定需要学习更多的 sql 请求。对于你给我的那个,我有一个小错误。 PG::UndefinedTable: ERROR: missing FROM-clause entry for table "groups_users" LINE 1: ..."users" WHERE (EXISTS (SELECT 1 FROM groups WHERE groups_use... 哈哈,我真的是EXISTING(存在?)支持者:)WHERE groups_use——这是你的错字,把_改成.groups.user_id)。跨度> PG::UndefinedColumn: ERROR: column groups.user_id does not exist LINE 1: ..."users" WHERE (EXISTS (SELECT 1 FROM groups WHERE groups.use... 你的版本 @eirikir 它不允许做我在这里所做的事情(而且我经常面临这样的任务)——用WHERE EXISTS (some join condition) 进行查询。好吧,可以编写GroupUser.where('group_id IN (?)', [1,2,3]).where('user_id = users.id').to_sql 来代替内部 SQL,但这并没有真正的帮助。 @BeniMio 那是无效的方法,因为它会用 Ruby 代码减去记录,而不是在数据库内部。因此,您会浪费时间来回传输大约两倍的数据,并且会损失 CPU/RAM,因为 Ruby 在计算方面的效率低于数据库引擎(用 C 语言编写并针对任务进行了优化)。在正常情况下,您希望将此类事情委托给数据库引擎。【参考方案2】:

现在您可以使用Where Exists gem。 (完全披露:我最近创建了那个宝石。)

有了它,您可以简单地完成您的任务:

User.where_exists(:groups, id: [1, 2]).where_not_exists(:groups, id: [3, 4])

【讨论】:

以上是关于使用 activerecord 发出请求以仅从组中获取用户,而不从其他人中获取的主要内容,如果未能解决你的问题,请参考以下文章

如何仅从组中查询具有最新时间戳的文档?

按值(不是列)分组后从组中选择一个随机条目?

聚合 SQL 函数以仅从每个组中获取第一个

SQL Server 查询需要从组中提取数据

强制 Ansible 从组中收集事实

.NET 正则表达式分组:从组中排除字符串