如何用ActiveRecord / Rails表达NOT IN查询?


207

只是为了更新此内容,因为似乎有很多人对此进行了介绍,如果您使用的是Rails 4,请查看TrungLê`和VinniVidiVicci的答案。

Topic.where.not(forum_id:@forums.map(&:id))

Topic.where(published:true).where.not(forum_id:@forums.map(&:id))

我希望有一个不涉及的简单解决方案find_by_sql,如果没有的话,我想那将是可行的。

我发现这篇文章引用了以下内容:

Topic.find(:all, :conditions => { :forum_id => @forums.map(&:id) })

这与

SELECT * FROM topics WHERE forum_id IN (<@forum ids>)

我想知道是否有办法做到NOT IN这一点,例如:

SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)

3
仅供参考,Datamapper对NOT IN具有特定的支持。示例:Person.all(:name.not => ['bob','rick','steve'])
马克·托马斯

1
抱歉,您无知,但是Datamapper是什么?是轨道3的一部分吗?
Toby Joiner 2010年

2
数据映射器是存储数据的另一种方法,它用不同的结构替换Active Record,然后以不同的方式编写与模型相关的内容,例如查询。
Michael Durrant

Answers:


313

Rails 4+:

Article.where.not(title: ['Rails 3', 'Rails 5']) 

Rails 3:

Topic.where('id NOT IN (?)', Array.wrap(actions))

actions数组在哪里:[1,2,3,4,5]


1
这是最新的活动记录查询模型的正确方法
Nevir

5
@NewAlexandria是正确的,因此您必须执行Topic.where('id NOT IN (?)', (actions.empty? ? '', actions)。它仍然会在nil上中断,但是我发现您传入的数组通常是由过滤器生成的,该过滤器[]至少会返回并且永远不会为nil。我建议您查看Squeel,它是Active Record之上的DSL。然后,您可以执行:Topic.where{id.not_in actions},nil / empty /或其他方式。
danneu 2013年

6
@danneu换来.empty?的话.blank?,您将
无所适从

@daaneu的(actions.empty??'',actions)应该是(actions.empty??'':actions)
marcel salathe 2014年

3
请使用rails 4表示法:Article.where.not(title:['Rails 3','Rails 5'])
2015年

152

仅供参考,在Rails 4中,您可以使用以下not语法:

Article.where.not(title: ['Rails 3', 'Rails 5'])

11
最后!他们花了这么长时间才包括在内吗?:)
Dominik Goltermann

50

您可以尝试类似:

Topic.find(:all, :conditions => ['forum_id not in (?)', @forums.map(&:id)])

您可能需要做@forums.map(&:id).join(',')。我不记得Rails是否可以将参数枚举到CSV列表中。

您也可以这样做:

# in topic.rb
named_scope :not_in_forums, lambda { |forums| { :conditions => ['forum_id not in (?)', forums.select(&:id).join(',')] }

# in your controller 
Topic.not_in_forums(@forums)

50

使用Arel:

topics=Topic.arel_table
Topic.where(topics[:forum_id].not_in(@forum_ids))

或者,如果首选:

topics=Topic.arel_table
Topic.where(topics[:forum_id].in(@forum_ids).not)

由于rails 4在:

topics=Topic.arel_table
Topic.where.not(topics[:forum_id].in(@forum_ids))

请注意,最终您最终不希望forum_ids成为ID列表,而希望成为子查询,如果是这样,那么在获取主题之前,您应该执行以下操作:

@forum_ids = Forum.where(/*whatever conditions are desirable*/).select(:id)

这样,您可以在单个查询中获得所有内容:

select * from topic 
where forum_id in (select id 
                   from forum 
                   where /*whatever conditions are desirable*/)

还要注意,最终您最终不想执行此操作,而是执行联接-可能会更有效。


2
联接可能更有效,但不一定。请务必使用EXPLAIN
詹姆斯

20

要扩展@TrungLê答案,请在Rails 4中执行以下操作:

Topic.where.not(forum_id:@forums.map(&:id))

您可以再进一步。如果您需要先仅过滤已发布的主题,然后过滤掉不需要的ID,则可以执行以下操作:

Topic.where(published:true).where.not(forum_id:@forums.map(&:id))

Rails 4使它变得如此简单!


12

如果@forums为空,则接受的解决方案将失败。要解决此问题,我必须要做

Topic.find(:all, :conditions => ['forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id))])

或者,如果使用Rails 3+:

Topic.where( 'forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id)) ).all

4

上面的大多数答案都可以满足您的要求,但是如果您要进行更多这样的谓词和复杂组合操作,请查看 Squeel。您将可以执行以下操作:

Topic.where{{forum_id.not_in => @forums.map(&:id)}}
Topic.where{forum_id.not_in @forums.map(&:id)} 
Topic.where{forum_id << @forums.map(&:id)}

2

您可能想看看Ernie Miller 的meta_where插件。您的SQL语句:

SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)

...可以这样表示:

Topic.where(:forum_id.nin => @forum_ids)

Railscasts的Ryan Bates创建了一个不错的截屏,解释了MetaWhere

不确定这是否是您要查找的内容,但是在我看来,它肯定比嵌入式SQL查询要好。


2

最初的帖子专门提到了使用数字ID,但是我来这里的目的是寻找对字符串数组进行NOT IN的语法。

ActiveRecord也会为您很好地处理此问题:

Thing.where(['state NOT IN (?)', %w{state1 state2}])

1

可以务实的方式制定出这些论坛ID吗?例如,您能以某种方式找到这些论坛吗?如果是这样,您应该做一些类似的事情

Topic.all(:joins => "left join forums on (forums.id = topics.forum_id and some_condition)", :conditions => "forums.id is null")

这比做SQL更有效 not in


1

这种方式优化了可读性,但是在数据库查询方面却没有那么高效:

# Retrieve all topics, then use array subtraction to
# find the ones not in our list
Topic.all - @forums.map(&:id)

0

您可以在以下条件下使用sql:

Topic.find(:all, :conditions => [ "forum_id NOT IN (?)", @forums.map(&:id)])


0

查询空白数组时,在where块中向数组添加“ << 0”,这样它就不会返回“ NULL”并中断查询。

Topic.where('id not in (?)',actions << 0)

如果操作可以是空数组或空数组。


1
警告:这实际上为数组添加了0,因此不再为空。它还具有修改数组的副作用-如果以后使用它,则有双重危险。最好将其包装在if-else中,并在边缘情况下使用Topic.none / all
Ted Pennings

一个更安全的方法是:Topic.where("id NOT IN (?)", actions.presence || [0])
韦斯顿·冈格

0

这是一个更复杂的“不在”查询,使用squeel在rails 4中使用子查询。与等效的sql相比,当然很慢,但是,它可以工作。

    scope :translations_not_in_english, ->(calmapp_version_id, language_iso_code){
      join_to_cavs_tls_arr(calmapp_version_id).
      joins_to_tl_arr.
      where{ tl1.iso_code == 'en' }.
      where{ cavtl1.calmapp_version_id == my{calmapp_version_id}}.
      where{ dot_key_code << (Translation.
        join_to_cavs_tls_arr(calmapp_version_id).
        joins_to_tl_arr.    
        where{ tl1.iso_code == my{language_iso_code} }.
        select{ "dot_key_code" }.all)}
    }

该范围中的前两个方法是其他范围,这些范围声明了别名cavtl1和tl1。<<是不正常的操作符。

希望这对某人有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.