2010-11-29 11 views
175

Nur um dies zu aktualisieren, da es scheint, dass eine Menge Leute dazu kommen, wenn Sie Rails 4 verwenden, schauen Sie sich die Antworten von Trung Lê` und VinniVidiVicci an.Wie eine NOT IN-Abfrage mit ActiveRecord/Rails auszudrücken?

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

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

ich es gehofft habe, ist eine einfache Lösung, die nicht find_by_sql mit sich bringt, wenn nicht, dann denke ich, dass wird arbeiten.

fand ich this article, die diese verweist:

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

, die die gleiche ist wie

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

Ich frage mich, ob es einen Weg gibt NOT IN damit zu tun, wie:

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

Als eine FYI, Datamapper hatte spezielle Unterstützung für NOT IN. Beispiel: 'Person.all (: name.not => ['bob', 'rick', 'steve'])' –

+1

Entschuldigung, dass ich ignorant bin, aber was ist Datamapper? ist das Teil der Schienen 3? –

+2

Data Mapper ist eine alternative Möglichkeit zum Speichern von Daten, ersetzt Active Record durch eine andere Struktur und dann schreiben Sie Ihre modellbezogenen Sachen wie Abfragen anders. –

Antwort

258

Ich verwende diese:

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

Wo actions mit ein Array ist: [1,2,3,4,5]

Edit:

Für Rails 4 Notation:

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

Dies ist der richtige Ansatz mit dem neuesten Active Record - Abfragemodell – Nevir

+35

, funktioniert aber nicht, wenn 'actions' gleich null oder leer ist. –

+5

@NewAlexandria hat Recht, Sie müssten also etwas tun wie' Topic.where ('id NOT IN') (?) ', (actions.empty??' ', actions) '. Es würde immer noch auf Null gehen, aber ich finde, dass das Array, das Sie übergeben, normalerweise von einem Filter erzeugt wird, der' [] 'zurückgibt Ich empfehle Squeel, ein DSL über Active Record. Dann könntest du: 'Topic.where {id.not_in actions}', nil/empty oder sonst. – danneu

50

Sie können versuchen, etwas wie:

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

Möglicherweise müssen Sie @forums.map(&:id).join(',') tun. Ich kann mich nicht erinnern, ob Rails das Argument in eine CSV-Liste bringt, wenn es aufzählbar ist.

Sie könnten auch dies tun:

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

Seltsam. Vielleicht ist es mit der Feldbenennung zu tun ... Ich habe das vorher nicht gesehen ... – jonnii

+1

@forums.pluck (: id) ist viel schneller. –

0

Sie SQL in Ihren Bedingungen verwenden können:

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

Können diese Forum auf pragmatische Weise werden ids ausgearbeitet? z.B. Sie können diese Foren irgendwie finden - wenn das der Fall ist, sollten Sie so etwas wie

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

tun, die eine SQL not in

50

Mit Arel als tun effizienter wäre:

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

oder, falls bevorzugt:

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

und seit rails 4 on:

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

Bitte beachten Sie, dass schließlich wollen Sie nicht die forum_ids die ids Liste sein, sondern eine Unterabfrage, wenn ja, dann sollten Sie die Themen etwas tun, bevor man:

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

in So kann man alles in einer einzigen Abfrage erhalten: so etwas wie:

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

Beachten Sie auch, dass schließlich wollen Sie nicht, dies zu tun, sondern vielmehr eine beitreten - was könnte mehr efficien sein t.

+1

Sauber und zukunftssicher! – rxgx

+2

Ein Beitritt _might_ effizienter sein, aber nicht unbedingt. Stellen Sie sicher, dass Sie 'EXPLAIN' verwenden! – James

2

Vielleicht möchten Sie einen Blick auf die meta_where plugin von Ernie Miller werfen.Ihre SQL-Anweisung:

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

... wie folgt ausgedrückt werden:

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

Ryan Bates von Railscasts erstellt eine nice screencast explaining MetaWhere.

Nicht sicher, ob dies das ist, was Sie suchen, aber für meine Augen sieht es sicherlich besser aus als eine eingebettete SQL-Abfrage.

4

Die meisten der Antworten oben sollten Ihnen genügen, aber wenn Sie viel mehr solcher tun Prädikat und komplexe Kombinationen auschecken Squeel.

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

Die akzeptierte Lösung schlägt fehl, wenn @forums leer ist: so etwas wie zu tun in der Lage Sie werden. Zur Umgehung dieses hatte ich

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

Oder, wenn mit Rails 3+ zu tun:

Topic.where('forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id))).all 
+3

funktioniert nicht in postgresql –

1

Auf diese Weise optimiert, um die Lesbarkeit, aber es ist nicht so effizient in Bezug auf die Datenbankabfragen:

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

Die ursprüngliche Post erwähnt speziell numerisches IDs verwenden, aber ich kam hier für die Syntax suche einen NOT IN mit einer Reihe von Strings zu tun.

Active wird damit umgehen schön für Sie auch:

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

FYI, In Rails 4 können Sie not Syntax:

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

endlich! Was hat sie so lange gedauert? :) –

0

Wenn Sie ein leeres Array Abfrage hinzufügen "< < 0 "an das Array im where-Block, so dass es nicht" NULL "zurückgibt und die Abfrage zu brechen.

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

Wenn Aktionen ein leeres oder leeres Array sein könnten.

+1

Warnung: Dies fügt dem Array tatsächlich eine 0 hinzu, so dass es nicht mehr leer ist. Es hat auch den Nebeneffekt, das Array zu modifizieren - doppelte Gefahr, wenn Sie es später verwenden. Viel besser, es in ein if-else zu verpacken und Topic.none/all für die Edge Cases –

17

Um auf @Trung Lê Antwort zu erweitern, in Rails 4 Sie Folgendes tun:

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

Und man konnte es noch einen Schritt weiter. Wenn Sie nur veröffentlichte Themen ersten Filter benötigen und dann Filter aus Iden Sie wollen nicht, Sie könnten dies tun:

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

Rails 4 macht es so viel einfacher!

+0

@ forens.pluck (: id) zu verwenden ist viel schneller. –

0

Hier ist eine komplexere "nicht in" Abfrage, mit einer Unterabfrage in Schienen 4 mit Quietsche. Natürlich sehr langsam im Vergleich zu den entsprechenden SQL, aber hey, es funktioniert.

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

Die ersten beiden Methoden im Bereich sind andere Bereiche, die die Aliase cavtl1 und tl1 deklarieren. < < ist der nicht in der Hand in Quietschen.

Ich hoffe, das hilft jemandem.

Verwandte Themen