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

Juste pour mettre à jour cela car il semble que beaucoup de gens viennent à cela, si vous utilisez Rails 4, regardez les réponses de Trung Lê` et 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 qui n’implique 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 la même que

 SELECT * FROM topics WHERE forum_id IN () 

Je me demande s’il y a un moyen de ne NOT IN faire avec ça, comme:

 SELECT * FROM topics WHERE forum_id NOT IN () 

    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']) 

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

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

    Vous pouvez essayer quelque chose comme:

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

    Vous devrez peut-être faire @forums.map(&:id).join(',') . Je ne me souviens plus si Rails contiendra l’argument dans une liste CSV s’il est énumérable.

    Vous pourriez 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) 

    En utilisant Arel:

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

    ou, si préféré:

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

    et depuis les rails 4 sur:

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

    Veuillez noter que vous ne voulez pas que les forum_ids soient la liste des identifiants, mais plutôt une sous-requête. Dans ce cas, vous devriez faire quelque chose comme ça avant de recevoir les sujets:

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

    de cette façon, vous obtenez tout en 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 finalement vous ne voulez pas faire cela, mais plutôt une jointure – ce qui pourrait être plus efficace.

    Pour développer la réponse à @Trung Lê, 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 Rubriques publiées, puis filtrer les ID que vous ne voulez pas, vous pouvez le faire:

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

    Rails 4 facilite les choses!

    La solution acceptée échoue si @forums est vide. Pour contourner ce problème 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 

    La plupart des réponses ci-dessus devraient vous suffire, mais si vous faites beaucoup plus de ces 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)} 

    Vous voudrez peut-être jeter un œil au plug-in 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 beau 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 intégrée.

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

    Qui serait plus efficace que de faire un SQL not in

    Cette méthode optimise la lisibilité, mais elle n’est pas aussi efficace en termes de requêtes de firebase database:

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

    Le post original mentionne spécifiquement l’utilisation d’identifiants numériques, mais je suis venu ici pour chercher la syntaxe pour faire un NOT IN avec un tableau de chaînes.

    ActiveRecord va bien gérer cela pour vous aussi:

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

    Vous pouvez utiliser sql dans vos conditions:

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

    Piggybacking de jonnii:

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

    utiliser un pincement plutôt que de cartographier les éléments

    trouvé via railsconf 2012 10 choses que vous ne saviez pas que les rails pourraient faire

    Lorsque vous interrogez un tableau vide, ajoutez “< < 0" au tableau dans le bloc where afin qu'il ne retourne pas "NULL" et interrompe la requête.

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

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

    Voici une requête plus complexe “pas dans”, en utilisant une sous-requête dans les rails 4 en utilisant squeel. Bien sûr, très lent comparé au sql équivalent, 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 de la scope sont d’autres scopes qui déclarent les alias cavtl1 et tl1. < < est l'opérateur non en squeel.

    J'espère que cela aide quelqu'un.