Как выразить запрос NOT IN с ActiveRecord / Rails? - PullRequest
191 голосов
/ 29 ноября 2010

Просто чтобы обновить это, так как кажется, что многие приходят к этому, если вы используете Rails 4, посмотрите ответы Трунга Лэ и Виннивидичи.

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>)

Ответы [ 15 ]

286 голосов
/ 25 июля 2011

Я использую это:

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

Где actions - массив с: [1,2,3,4,5]

Edit:

Для обозначения Rails 4:

Article.where.not(title: ['Rails 3', 'Rails 5']) 
147 голосов
/ 16 августа 2013

FYI, в Rails 4 вы можете использовать синтаксис not:

Article.where.not(title: ['Rails 3', 'Rails 5'])
50 голосов
/ 14 апреля 2011

Использование 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)

и начиная с рельсов 4:

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

Обратите внимание, что в конечном итоге вы не хотите, чтобы forum_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*/)

Также обратите внимание, что в конечном итоге вы не хотите делать это, а хотите объединить - что может быть более эффективным.

50 голосов
/ 29 ноября 2010

Вы можете попробовать что-то вроде:

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)
17 голосов
/ 22 января 2014

Чтобы расширить ответ @Trung Lê, в Rails 4 вы можете сделать следующее:

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

И вы могли бы сделать еще один шаг вперед. Если вам нужно сначала отфильтровать только опубликованные темы и , то отфильтровать ненужные идентификаторы, вы можете сделать это:

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

Rails 4 делает это намного проще!

12 голосов
/ 05 октября 2011

Принятое решение не будет выполнено, если @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 голосов
/ 14 сентября 2011

Большинство из приведенных выше ответов должно быть вам достаточно, но если вы делаете гораздо больше таких предикатов и сложных комбинаций, проверьте 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 голосов
/ 18 апреля 2011

Возможно, вы захотите взглянуть на плагин meta_where от Эрни Миллера. Ваш оператор SQL:

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

... можно выразить так:

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

Райан Бейтс из Railscasts создал хороший скринкаст, объясняющий MetaWhere .

Не уверен, что это то, что вы ищете, но, на мой взгляд, это выглядит лучше, чем встроенный SQL-запрос.

1 голос
/ 20 июня 2013

В оригинальном сообщении специально упоминается использование числовых идентификаторов, но я пришел сюда в поисках синтаксиса для NOT IN с массивом строк.

ActiveRecord справится с этим и вам:

Thing.where(['state NOT IN (?)', %w{state1 state2}])
1 голос
/ 28 января 2012

Этот способ оптимизирует читабельность, но не так эффективен с точки зрения запросов к базе данных:

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