web-dev-qa-db-fra.com

Comment exprimer une requête NOT IN avec ActiveRecord/Rails?

Juste pour mettre à jour ceci car il semble que beaucoup de gens y viennent, si vous utilisez Rails 4, regardez les réponses de Trung Lê` et de VinniVidiVicci.

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

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

J'espère qu'il existe une solution simple n'impliquant pas find_by_sql, sinon je suppose que cela devra fonctionner.

J'ai trouvé cet article qui fait référence à ceci:

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

qui est le même que 

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

Je me demande s’il existe un moyen de faire NOT IN avec cela, comme:

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

J'utilise ceci:

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

actions est un tableau avec: [1,2,3,4,5]

Modifier:

Pour la notation Rails 4:

Article.where.not(title: ['Rails 3', 'Rails 5']) 
278
José Castro

Pour info, dans Rails 4, vous pouvez utiliser la syntaxe not:

Article.where.not(title: ['Rails 3', 'Rails 5'])
146
Trung Lê

Utiliser Arel:

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

ou, si vous préférez:

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

et depuis Rails 4 sur:

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

Veuillez noter que vous ne souhaitez pas que le forum soit la liste d'identifiants, mais plutôt une sous-requête. Si c'est le cas, vous devriez faire quelque chose comme ceci avant de lire les sujets:

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

de cette façon, vous obtenez tout dans une seule requête: quelque chose comme:

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

Notez également que vous ne voulez pas faire cela, mais plutôt une jointure - ce qui pourrait être plus efficace.

50
Pedro Morte Rolo

Vous pouvez essayer quelque chose comme:

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

Vous pourriez avoir besoin de faire @forums.map(&:id).join(','). Je ne me souviens pas si Rails sera l'argument dans une liste CSV si elle est énumérable.

Vous pouvez aussi faire ceci:

# 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
jonnii

Pour développer @Trung Lê answer, dans Rails 4, vous pouvez effectuer les opérations suivantes:

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

Et vous pourriez aller plus loin… .. Si vous devez d’abord filtrer uniquement les sujets publiés et puis filtrer les identifiants que vous ne voulez pas, vous pouvez le faire:

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

Rails 4, c'est tellement plus facile!

17
VinniVidiVicci

La solution acceptée échoue si @forums est vide. Pour contourner cela, je devais faire

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

Ou, si vous utilisez Rails 3+:

Topic.where( 'forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id)) ).all
12
Filipe Giusti

La plupart des réponses ci-dessus devraient vous suffire, mais si vous faites beaucoup plus de telles combinaisons de prédicats et complexes, consultez Squeel . Vous pourrez faire quelque chose comme: 

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
jake

Vous voudrez peut-être jeter un coup d’œil au plugin meta_where d’Ernie Miller. Votre déclaration SQL:

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

... pourrait être exprimé comme ceci:

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

Ryan Bates de Railscasts a créé un joli screencast expliquant MetaWhere .

Je ne sais pas si c'est ce que vous recherchez, mais à mes yeux, cela a certainement l'air mieux qu'une requête SQL embarquée.

2
Marcin Wyszynski

Ces identifiants de forum peuvent-ils être élaborés de manière pragmatique? par exemple. pouvez-vous trouver ces forums en quelque sorte - si tel est le cas, vous devriez faire quelque chose comme

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

Ce qui serait plus efficace que de faire un not in SQL

1
Omar Qureshi

L'article original mentionnait spécifiquement l'utilisation d'identifiants numériques, mais je suis venu ici pour chercher la syntaxe permettant de faire un NOT IN avec un tableau de chaînes.

ActiveRecord s'en chargera également pour vous:

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

Cela optimise la lisibilité, mais n'est pas aussi efficace en termes de requêtes de base de données:

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

Vous pouvez utiliser SQL dans vos conditions:

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

Voici une requête plus complexe "pas dans" utilisant une sous-requête dans Rails 4 avec squeel. Bien sûr, très lent comparé à l'équivalent sql, mais bon, ça marche.

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

Les deux premières méthodes du champ d'application sont d'autres domaines qui déclarent les alias cavtl1 et tl1. << est l'opérateur not in de squeel.

J'espère que ça aide quelqu'un. 

0
dukha

Lorsque vous interrogez un tableau vide, ajoutez "<< 0" au tableau du bloc where afin qu'il ne renvoie pas "NULL" et rompe la requête.

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

Si les actions peuvent être un tableau vide ou vide.

0
itsEconomics

Piggybacking de jonnii:

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

en utilisant plumer plutôt que la cartographie sur les éléments

trouvé via railsconf 2012 10 choses que vous ne saviez pas que Rails pouvait faire

0
Thomas Wolfe