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

Tob*_*ner 198 ruby-on-rails rails-activerecord

只是为了更新这个,因为似乎有很多人来到这里,如果你使用Rails 4,请查看TrungLê`和VinniVidiVicci的答案.

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

Topic.where(published:true).where.not(forum_id:@forums.map(&:id))
Run Code Online (Sandbox Code Playgroud)

我希望有一个简单的解决方案不涉及find_by_sql,如果不是,那么我想这将必须工作.

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

Topic.find(:all, :conditions => { :forum_id => @forums.map(&:id) })
Run Code Online (Sandbox Code Playgroud)

这是一样的

SELECT * FROM topics WHERE forum_id IN (<@forum ids>)
Run Code Online (Sandbox Code Playgroud)

我想知道是否有办法NOT IN解决这个问题,例如:

SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)
Run Code Online (Sandbox Code Playgroud)

Jos*_*tro 295

我正在使用这个:

Topic.where('id NOT IN (?)', Array.wrap(actions))
Run Code Online (Sandbox Code Playgroud)

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

编辑:

对于Rails 4表示法:

Article.where.not(title: ['Rails 3', 'Rails 5']) 
Run Code Online (Sandbox Code Playgroud)

  • 但如果`actions`为零或空,则不起作用 (38认同)
  • @danneu只是交换`.empty?`for .blank?`而且你没有证据 (6认同)
  • @NewAlexandria是对的,所以你必须做一些像`Topic.where('id not IN(?)',(actions.empty??'',actions)`.它仍然会打破零,但我发现你传入的数组通常是由一个过滤器生成的,它至少会返回`[]`而且永远不会为零.我建议检查Squeel,一个在Active Record之上的DSL.然后你可以这样做:`主题.其中{id.not_in actions}`,nil/empty /或其他. (5认同)
  • go for rails 4表示法:Article.where.not(标题:['Rails 3','Rails 5']) (3认同)
  • 就像 @NewAlexandria 所说,**当变量为 `[]` 或 `nil`** 时,这不起作用。你最终会得到“id NOT in (null)”。要解决这个问题,请使用 `presence`,如下所示: `Topic.where( 'id NOT IN (?)', actions.presence || "" )` (2认同)

Tru*_* Lê 151

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

Article.where.not(title: ['Rails 3', 'Rails 5'])
Run Code Online (Sandbox Code Playgroud)

  • 最后!是什么让他们这么久才包括那个?:) (11认同)

jon*_*nii 50

您可以尝试以下方式:

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

你可能需要这样做@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)
Run Code Online (Sandbox Code Playgroud)


Ped*_*olo 50

使用Arel:

topics=Topic.arel_table
Topic.where(topics[:forum_id].not_in(@forum_ids))
Run Code Online (Sandbox Code Playgroud)

或者,如果愿意:

topics=Topic.arel_table
Topic.where(topics[:forum_id].in(@forum_ids).not)
Run Code Online (Sandbox Code Playgroud)

并且因为导轨4上:

topics=Topic.arel_table
Topic.where.not(topics[:forum_id].in(@forum_ids))
Run Code Online (Sandbox Code Playgroud)

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

@forum_ids = Forum.where(/*whatever conditions are desirable*/).select(:id)
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以在一个查询中获得所有内容:类似于:

select * from topic 
where forum_id in (select id 
                   from forum 
                   where /*whatever conditions are desirable*/)
Run Code Online (Sandbox Code Playgroud)

还要注意,最终你不想这样做,而是一个连接 - 可能更有效.

  • 连接_might_更有效,但不一定.一定要使用`EXPLAIN`! (2认同)

Vin*_*ret 19

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

Topic.where.not(forum_id:@forums.map(&:id))
Run Code Online (Sandbox Code Playgroud)

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

Topic.where(published:true).where.not(forum_id:@forums.map(&:id))
Run Code Online (Sandbox Code Playgroud)

Rails 4让它变得如此简单!


Fil*_*sti 12

如果@forums为空,则接受的解决方案失败.要解决这个问题,我必须这样做

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

或者,如果使用Rails 3+:

Topic.where( 'forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id)) ).all
Run Code Online (Sandbox Code Playgroud)