我希望有一个简单的解决方案,不涉及find_by_sql,如果没有,那么我想这将不得不工作。

我发现这篇文章引用了这个:

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

哪个是一样的

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

我想知道是否有一种方法可以不这样做,比如:

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

你可以尝试这样做:

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)

你可以在你的条件下使用sql:

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

这些论坛id能够以一种实用的方式计算出来吗?例如,你能以某种方式找到这些论坛吗?如果是这样的话,你应该做一些事情

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

哪一个会比做一个SQL不在更有效


使用服装:

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在:

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

请注意,最终你不希望forum_ids是ids列表,而是一个子查询,如果是这样,那么你应该在获得主题之前做这样的事情:

@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*/)

还要注意,最终您并不想这样做,而是想要一个连接——这可能更有效。


你可能想看看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查询更好。


导轨 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]


上面的大多数答案应该足以满足您的需求,但如果您正在进行更多此类谓词和复杂组合,请查看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)}

如果@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

这种方法优化了可读性,但在数据库查询方面效率不高:

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

借助jonnii:

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

使用提取而不是在元素上进行映射

通过railsconf 2012找到你不知道rails可以做的10件事


最初的帖子特别提到了使用数字id,但我来这里寻找用字符串数组做NOT IN的语法。

ActiveRecord也会很好地为你处理:

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

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

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

当你查询一个空数组时,在where块中添加"<< 0"到数组中,这样它就不会返回"NULL"并中断查询。

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

If actions可以是空数组或空数组。


要扩展@Trung Lê答案,在Rails 4中您可以执行以下操作:

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

你可以更进一步。 如果你需要先过滤发布的主题,然后过滤掉你不想要的id,你可以这样做:

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

Rails 4让它变得更简单!


下面是一个更复杂的“not in”查询,使用rails 4中使用squeel的子查询。当然,与等效的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的其他作用域。<<是squeel中的not in操作符。

希望这能帮助到一些人。


如果有人想使用两个或更多的条件,你可以这样做:

your_array = [1,2,3,4]
your_string = "SOMETHING"

YourModel.where('variable1 NOT IN (?) AND variable2=(?)',Array.wrap(your_array),your_string)