使用 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 发出请求以仅从组中获取用户,而不从其他人中获取的主要内容,如果未能解决你的问题,请参考以下文章