Rails:如何选择没有特定相关(关联)对象的记录(SQL EXISTS简要操作方法)

假设我们有用户:

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

和团体:

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

基本上,标准的多对多连接:

 class Connection belongs_to :user belongs_to :group end 

我打算做的是:

  • 仅选择不属于给定的一组组的用户(具有ID的组[4,5,6]
  • 仅选择属于一组( [1,2,3] )且不属于另一组( [4,5,6] )的用户
  • 仅选择不属于组的用户

另外,我不想:

  • 从数据库中获取大量数据以使用Ruby代码对其进行操作。 我知道在CPU和内存方面效率低下(Ruby比任何常用的数据库引擎慢得多,通常我想依靠数据库引擎来完成繁重的工作)
  • 我尝试了像User.joins(:group).where(group_id: [1,2,3]).where.not(group_id: [4,5,6]) ,它们返回错误的结果(结果中的一些用户)集合属于组4,5,6 以及 1,2,3)
  • 我不想仅仅为了检查存在而进行连接,因为我知道对于DB来说这是一个非常复杂的(即CPU /内存密集型)操作

这些问题在初学者和中级Rails开发人员中非常普遍。 您知道ActiveRecord接口和基本的SQL操作,但您偶然发现了问题中概述的此类任务。 (这类问题的几个例子: 1,2 )。

答案很简单:使用SQL EXISTS条件 。 来自给定URL的快速参考:

句法

SQL EXISTS条件的语法是:

 WHERE EXISTS ( subquery ); 

参数或参数

子查询

子查询是SELECT语句。 如果子查询在其结果集中返回至少一个记录,则EXISTS子句将计算为true,并且将满足EXISTS条件。 如果子查询未返回任何记录,则EXISTS子句将评估为false,并且不会满足EXISTS条件。

还提到EXISTS可能比JOIN慢,但通常不是这样。 从Exists v。加入关于SO的问题:

EXISTS仅用于测试子查询是否返回结果,以及是否尽快发生短路。 JOIN用于通过将结果集与来自另一个具有关系的表中的其他字段进行组合来扩展结果集。 […]如果你有适当的索引,大多数时候EXISTS性能与JOIN相同。 例外情况是非常复杂的子查询,通常使用EXISTS会更快。

因此,数据库不需要查看所有连接(一旦找到正确的连接就停止’加入’记录’exists’),并且不需要返回连接表中的所有字段(只检查相应的行,确实存在)。

回答具体问题:

仅选择不属于给定的一组组的用户(具有ID的组[4,5,6]

 not_four_to_six = User.where("NOT EXISTS ( SELECT 1 FROM connections WHERE connections.user_id = users.id AND connections.group_id IN (?) )", [4,5,6]) 

仅选择属于一组( [1,2,3] )且不属于另一组( [4,5,6] )的用户

 one_two_three = not_four_to_six.where("EXISTS ( SELECT 1 FROM connections WHERE connections.user_id = users.id AND connections.group_id IN (?) )", [1,2,3]) 

仅选择不属于组的此类用户

 User.where("NOT EXISTS ( SELECT 1 FROM connections WHERE connections.user_id = users.id )")