Как выразить не в запросе с помощью ActiveRecord / Rails?

просто обновить это, так как кажется, что многие люди приходят к этому, если вы используете Rails 4 Посмотрите на ответы Trung Lê` и 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>)

15 ответов


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

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

здесь actions - это массив с: [1,2,3,4,5]

Edit:

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

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

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

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

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

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)

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

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


чтобы развернуть ответ @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 делает это намного проще!


принятое решение не выполняется, если @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

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

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

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

...может быть выражено так:

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

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

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


эти идентификаторы форума будут выработаны прагматично? например, вы можете найти эти форумы как - то-если это так, вы должны сделать что-то вроде

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

что было бы более эффективным, чем выполнение SQL not in


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

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

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

ActiveRecord будет обрабатывать это красиво для вас тоже:

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

вы можете использовать sql в своих условиях:

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

Piggybacking от Джонни:

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

используя pluck, а не отображение над элементами

нашли через railsconf 2012 10 вещей, которые вы не знали рельсы могли бы сделать


при запросе пустого массива добавьте"

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

если действия могут быть пустым или пустым массивом.


вот более сложный запрос "не в", используя подзапрос в 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)}
    }

первые 2 метода в области - это другие области, которые объявляют псевдонимы cavtl1 и tl1.

надеюсь, это кому-то поможет.