¿Cómo expresar una consulta NOT IN con ActiveRecord / Rails?


Solo para actualizar esto ya que parece que mucha gente viene a esto, si estás usando Rails 4 mira las respuestas de Trung Lê` y VinniVidiVicci.

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

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

Espero que haya una solución fácil que no implique find_by_sql, si no, entonces supongo que tendrá que funcionar.

Encontré este artículo que hace referencia a esto:

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

Que es lo mismo que

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

Me pregunto si hay una manera de hacer NOT IN con eso, como:

SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)
Author: user2262149, 2010-11-29

15 answers

Estoy usando esto:

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

Donde actions es una matriz con: [1,2,3,4,5]

Editar:

Para la notación de Rails 4:

Article.where.not(title: ['Rails 3', 'Rails 5']) 
 269
Author: José Castro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-28 22:43:24

Para su información, En Rails 4, puede usar not sintaxis:

Article.where.not(title: ['Rails 3', 'Rails 5'])
 145
Author: Trung Lê,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-16 00:21:38

Puedes probar algo como:

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

Es posible que tenga que hacer @forums.map(&:id).join(','). No puedo recordar si Rails introducirá el argumento en una lista CSV si es enumerable.

También podrías hacer esto:

# 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)
 50
Author: jonnii,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 01:04:52

Usando Arel:

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

O, si se prefiere:

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

Y desde raíles 4 en:

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

Tenga en cuenta que eventualmente no desea que los forum_ids sean la lista de ids, sino más bien una subconsulta, si es así, debe hacer algo como esto antes de obtener los temas:

@forum_ids = Forum.where(/*whatever conditions are desirable*/).select(:id)

De esta manera se obtiene todo en una sola consulta: algo así como:

select * from topic 
where forum_id in (select id 
                   from forum 
                   where /*whatever conditions are desirable*/)

También tenga en cuenta que con el tiempo no quiere hacer esto, sino más bien un join-lo que podría ser más eficiente.

 50
Author: Pedro Morte Rolo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-27 16:05:23

Para ampliar la respuesta de @Trung Lê, en Rails 4 puedes hacer lo siguiente:

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

Y podrías ir un paso más allá. Si primero necesita filtrar solo los temas publicados y luego filtrar los ID que no desea, puede hacer esto:

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

Rails 4 lo hace mucho más fácil!

 17
Author: VinniVidiVicci,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-22 16:08:29

La solución aceptada falla si @forums está vacía. Para solucionar esto tuve que hacer

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

O, si se utiliza Rails 3+:

Topic.where( 'forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id)) ).all
 12
Author: Filipe Giusti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 01:01:30

La mayoría de las respuestas anteriores deberían bastarte, pero si estás haciendo mucho más de tales predicados y combinaciones complejas, echa un vistazo a Squeel. Usted será capaz de hacer algo como:

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)}
 4
Author: jake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 00:59:48

Es posible que desee echar un vistazo a la meta_where plugin por Ernie Miller. Su sentencia SQL:

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

...se podría expresar así:

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

Ryan Bates de Railscasts creó un bonito screencast explicando MetaWhere .

No estoy seguro de si esto es lo que está buscando, pero a mis ojos ciertamente se ve mejor que una consulta SQL incrustada.

 2
Author: Marcin Wyszynski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 01:02:11

¿Se pueden elaborar estos id de foro de manera pragmática? por ejemplo, puede encontrar estos foros de alguna manera - si ese es el caso, debe hacer algo como

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

Que sería más eficiente que hacer un SQL not in

 1
Author: Omar Qureshi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-29 20:24:18

De esta manera optimiza la legibilidad, pero no es tan eficiente en términos de consultas de base de datos:

# Retrieve all topics, then use array subtraction to
# find the ones not in our list
Topic.all - @forums.map(&:id)
 1
Author: evanrmurphy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-28 07:31:20

El post original menciona específicamente el uso de identificadores numéricos, pero vine aquí buscando la sintaxis para hacer un NOT IN con una matriz de cadenas.

ActiveRecord manejará eso muy bien para ti también:

Thing.where(['state NOT IN (?)', %w{state1 state2}])
 1
Author: Andy Triggs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-20 11:42:41

Puede usar sql en sus condiciones:

Topic.find(:all, :conditions => [ "forum_id NOT IN (?)", @forums.map(&:id)])
 0
Author: tjeden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-29 19:58:26

Piggybacking fuera de jonnii:

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

Usando pluck en lugar de mapear sobre los elementos

Encontrado a través de railsconf 2012 10 cosas que no sabías que rails podría hacer

 0
Author: Thomas Wolfe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-13 17:15:15

Cuando consulta una matriz en blanco, agregue "

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

Si las acciones podrían ser una matriz vacía o en blanco.

 0
Author: itsEconomics,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-18 18:33:31

Aquí hay una consulta "not in" más compleja, usando una subconsulta en rails 4 usando squeel. Por supuesto muy lento en comparación con el equivalente sql, pero bueno, funciona.

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

Los primeros 2 métodos en el ámbito son otros ámbitos que declaran los alias cavtl1 y tl1.

Espero que esto ayude a alguien.

 0
Author: dukha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-10 04:09:42