Как выразить запрос NOT IN с помощью 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>)

Ответ 1

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

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

Где actions представляют собой массив с: [1,2,3,4,5]

Редактировать:

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

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

Ответ 2

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

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

Ответ 3

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

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)

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

... может быть выражено следующим образом:

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

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

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

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

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

ActiveRecord прекрасно справится с этим:

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

Ответ 12

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

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

Ответ 14

Когда вы запрашиваете пустой массив, добавьте "< < 0" к массиву в блоке where, чтобы он не возвращал "NULL" и не разбивал запрос.

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

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

Ответ 15

Вот более сложный запрос "не в", используя подзапрос в 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. & Л; < это не оператор в squeel.

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