ruby on rails - query - ¿Cómo se expresa una consulta NOT IN con ActiveRecord/Rails?
rails sql query (15)
¿Pueden estos identificadores de foro elaborarse de forma 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
Solo para actualizar esto ya que parece que mucha gente viene a esto, si está utilizando Rails 4, mire 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 involucre 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 NOT IN
con eso, como:
SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)
Aquí hay una consulta más compleja "no disponible", usando una subconsulta en rieles 4 usando squeel. Por supuesto, es muy lento en comparación con el sql equivalente, 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 alcance son otros ámbitos que declaran los alias cavtl1 y tl1. << es el operador no en squeel.
Espero que esto ayude a alguien.
Cuando consulta una matriz en blanco, agregue "<< 0" a la matriz en el bloque where para que no devuelva "NULO" y rompa la consulta.
Topic.where(''id not in (?)'',actions << 0)
Si las acciones pueden ser una matriz vacía o en blanco.
De esta manera se optimiza para la legibilidad, pero no es tan eficiente en términos de consultas a la base de datos:
# Retrieve all topics, then use array subtraction to
# find the ones not in our list
Topic.all - @forums.map(&:id)
Es posible que desee echarle un vistazo al complemento meta_where de Ernie Miller. Su declaración SQL:
SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)
... podría expresarse así:
Topic.where(:forum_id.nin => @forum_ids)
Ryan Bates de Railscasts creó un buen 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.
Estoy usando esto:
Topic.where(''id NOT IN (?)'',actions)
Donde las actions
son una matriz con: [1,2,3,4,5]
Editar:
Para la notación Rails 4:
Article.where.not(title: [''Rails 3'', ''Rails 5''])
FYI, en Rails 4, puede usar not
sintaxis:
Article.where.not(title: [''Rails 3'', ''Rails 5''])
La mayoría de las respuestas anteriores deberían bastarle, pero si está haciendo mucho más de ese predicado y combinaciones complejas, consulte Squeel . Podrás 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)}
La publicación original menciona específicamente el uso de ID numéricos, pero vine aquí en busca de la sintaxis para hacer NOT IN con una matriz de cadenas.
ActiveRecord lo manejará muy bien también:
Thing.where([''state NOT IN (?)'', %w{state1 state2}])
La solución aceptada falla si @forums
está vacío. Para solucionar esto, tuve que hacer
Topic.find(:all, :conditions => [''forum_id not in (?)'', (@forums.empty? ? '''' : @forums.map(&:id))])
O, si usa Rails 3+:
Topic.where( ''forum_id not in (?)'', (@forums.empty? ? '''' : @forums.map(&:id)) ).all
Para expandir @Trung Lê answer, en Rails 4 puede hacer lo siguiente:
Topic.where.not(forum_id:@forums.map(&:id))
Y podrías dar un paso más. Si primero necesita filtrar solo los temas publicados y luego filtra 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!
Piggybacking de jonnii:
Topic.find(:all, :conditions => [''forum_id not in (?)'', @forums.pluck(:id)])
usando desplumar en lugar de mapear los elementos
encontrado a través de railsconf 2012 10 cosas que no sabías que los rieles podían hacer
Puedes probar algo como:
Topic.find(:all, :conditions => [''forum_id not in (?)'', @forums.map(&:id)])
Es posible que deba hacer @forums.map(&:id).join('','')
. No puedo recordar si Rails incluirá el argumento en una lista CSV si es enumerable.
También puedes 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)
Puedes usar sql en tus condiciones:
Topic.find(:all, :conditions => [ "forum_id NOT IN (?)", @forums.map(&:id)])
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 los rieles 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, en caso afirmativo, debe hacer algo como esto antes de obtener los temas:
@forum_ids = Forum.where(/*whatever conditions are desirable*/).select(:id)
de esta manera, obtienes 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 note que eventualmente no quiere hacer esto, sino una unión, lo que podría ser más eficiente.