235 votes

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

J'espère qu'il y a une solution facile qui n'implique pas find_by_sql Si ce n'est pas le cas, je suppose que cela devra fonctionner.

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

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

qui est identique à

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

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

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

3 votes

Pour information, Datamapper a un support spécifique pour le NOT IN. Exemple : Person.all(:name.not => ['bob','rick','steve'])

1 votes

Désolé d'être ignorant, mais qu'est-ce que Datamapper ? fait-il partie de rails 3 ?

2 votes

Le mappeur de données est un moyen alternatif de stocker les données, il remplace Active Record par une structure différente et vous écrivez alors différemment les choses liées au modèle, comme les requêtes.

360voto

JosephCastro Points 1197

Rails 4+ :

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

Rails 3 :

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

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

1 votes

C'est la bonne approche avec le dernier modèle de requête Active Record.

6 votes

@NewAlexandria a raison, donc vous devriez faire quelque chose comme Topic.where('id NOT IN (?)', (actions.empty? ? '', actions) . Cela se casserait toujours sur nil, mais je trouve que le tableau que vous passez est généralement généré par un filtre qui retournera [] au minimum et jamais nul. Je vous recommande de consulter Squeel, un DSL au-dessus d'Active Record. Vous pourriez alors le faire : Topic.where{id.not_in actions} , nil/empty/or sinon.

6 votes

@danneu just swap .empty? para .blank? et vous êtes à l'abri du néant

158voto

Trung Lê Points 1807

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

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

11 votes

Enfin ! qu'est-ce qui leur a pris si longtemps pour inclure cela ? :)

53voto

Pedro Morte Rolo Points 5171

En utilisant 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 les rails 4 :

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

Veuillez noter qu'éventuellement vous ne voulez pas que le forum_ids soit la liste des ids, mais plutôt une sous-requête, si c'est le cas, vous devriez faire quelque chose comme ceci avant de récupérer 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 qu'éventuellement vous ne voulez pas faire cela, mais plutôt une jointure - ce qui pourrait être plus efficace.

2 votes

Un joint pourrait être plus efficace, mais pas nécessairement. Veillez à utiliser EXPLAIN !

49voto

jonnii Points 17046

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 transforme l'argument en une liste CSV s'il est énumérable.

Vous pouvez aussi faire ça :

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

25voto

VinniVidiVicci Points 51

Pour développer la réponse de @Trung Lê, dans Rails 4, vous pouvez faire ce qui suit :

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

Et tu pourrais aller encore plus loin. Si vous devez d'abord filtrer uniquement les sujets publiés et puis pour filtrer les identifiants que vous ne voulez pas, vous pouvez faire ceci :

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

Rails 4 rend les choses tellement plus faciles !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X