Wie drücke ich eine NOT IN-Abfrage mit ActiveRecord / Rails aus?

207

Nur um dies zu aktualisieren, da es scheint, dass viele Leute dazu kommen, wenn Sie Rails 4 verwenden, schauen Sie sich die Antworten von Trung Lê` und VinniVidiVicci an.

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

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

Ich hoffe, es gibt eine einfache Lösung, die nicht beinhaltet find_by_sql, wenn nicht, dann muss das wohl funktionieren.

Ich habe diesen Artikel gefunden, der darauf verweist:

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

das ist das gleiche wie

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

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

SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)
Toby Tischler
quelle
3
Als FYI hat Datamapper spezielle Unterstützung für NOT IN erhalten. Beispiel:Person.all(:name.not => ['bob','rick','steve'])
Mark Thomas
1
Entschuldigung, dass Sie unwissend sind, aber was ist Datamapper? Ist das ein Teil von Schienen 3?
Toby Joiner
2
Data Mapper ist eine alternative Methode zum Speichern von Daten. Er ersetzt Active Record durch eine andere Struktur, und dann schreiben Sie Ihre modellbezogenen Elemente wie Abfragen anders.
Michael Durrant

Antworten:

313

Schienen 4+:

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

Schienen 3:

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

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

José Castro
quelle
1
Dies ist der richtige Ansatz mit dem neuesten Active Record-Abfragemodell
Nevir
5
@NewAlexandria ist richtig, also müsstest du so etwas tun Topic.where('id NOT IN (?)', (actions.empty? ? '', actions). Es würde immer noch auf Null brechen, aber ich finde, dass das Array, das Sie übergeben, normalerweise von einem Filter generiert wird, []der mindestens und niemals Null zurückgibt. Ich empfehle Squeel, ein DSL über Active Record. Dann könnten Sie tun : Topic.where{id.not_in actions}, null / leer / oder anders.
Danneu
6
@ Danneu tauschen Sie einfach .empty?für .blank?und Sie sind null-
sicher
(aktivitäten.leer ?? '', aktionen) von @daaneu sollte sein (aktionen.leer?? '':
aktionen
3
Gehen Sie für die Rails 4 Notation: Article.where.not (Titel: ['Rails 3', 'Rails 5'])
Tal
152

Zu Ihrer Information: In Rails 4 können Sie die folgende notSyntax verwenden:

Article.where.not(title: ['Rails 3', 'Rails 5'])
Trung Lê
quelle
11
endlich! Warum haben sie so lange gebraucht, um das aufzunehmen? :)
Dominik Goltermann
50

Sie können etwas versuchen wie:

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

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

Sie können dies auch 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)
jonnii
quelle
50

Verwenden von Arel:

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 da Schienen 4 auf:

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

Bitte beachten Sie, dass Sie eventuell nicht möchten, dass die forum_ids die IDs-Liste sind, sondern eine Unterabfrage. Wenn ja, sollten Sie Folgendes tun, bevor Sie die Themen abrufen:

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

Auf diese Weise erhalten Sie alles in einer einzigen Abfrage: so etwas wie:

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

Beachten Sie auch, dass Sie dies eventuell nicht mehr tun möchten, sondern einen Join - was möglicherweise effizienter ist.

Pedro Rolo
quelle
2
Ein Join ist möglicherweise effizienter, aber nicht unbedingt. Stellen Sie sicher, zu verwenden EXPLAIN!
James
20

Um die Antwort von @Trung Lê zu erweitern, können Sie in Rails 4 Folgendes tun:

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

Und Sie könnten noch einen Schritt weiter gehen. Wenn Sie zuerst nur nach veröffentlichten Themen filtern und dann die nicht gewünschten IDs herausfiltern müssen, können Sie Folgendes tun:

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

Rails 4 macht es so viel einfacher!

Vincent Cadoret
quelle
12

Die akzeptierte Lösung schlägt fehl, wenn sie @forumsleer ist. Um dies zu umgehen, musste ich tun

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

Oder wenn Sie Rails 3+ verwenden:

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

Die meisten der obigen Antworten sollten ausreichen, aber wenn Sie viel mehr solcher Prädikate und komplexen Kombinationen machen, schauen Sie sich Squeel an . Sie können Folgendes tun:

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)}
jake
quelle
2

Vielleicht möchten Sie sich das meta_where-Plugin von Ernie Miller ansehen . Ihre SQL-Anweisung:

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

... könnte so ausgedrückt werden:

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

Ryan Bates von Railscasts hat einen schönen Screencast erstellt, der MetaWhere erklärt .

Ich bin mir nicht sicher, ob dies das ist, wonach Sie suchen, aber in meinen Augen sieht es sicherlich besser aus als eine eingebettete SQL-Abfrage.

Marcin Wyszynski
quelle
2

Der ursprüngliche Beitrag erwähnt ausdrücklich die Verwendung numerischer IDs, aber ich bin hierher gekommen, um nach der Syntax für ein NOT IN mit einem Array von Zeichenfolgen zu suchen.

ActiveRecord erledigt das auch für Sie:

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

Können diese Forum-IDs pragmatisch ausgearbeitet werden? zB kannst du diese Foren irgendwie finden - wenn das der Fall ist, solltest du so etwas tun

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

Was effizienter wäre als ein SQL not in

Omar Qureshi
quelle
1

Diese Methode optimiert die Lesbarkeit, ist jedoch in Bezug auf Datenbankabfragen nicht so effizient:

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

Sie können SQL in Ihren Bedingungen verwenden:

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

Wenn Sie ein leeres Array abfragen, fügen Sie dem Array im where-Block "<< 0" hinzu, damit es nicht "NULL" zurückgibt und die Abfrage unterbricht.

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

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

seine Wirtschaft
quelle
1
Warnung: Dadurch wird dem Array tatsächlich eine 0 hinzugefügt, sodass es nicht mehr leer ist. Es hat auch den Nebeneffekt, dass das Array geändert wird - doppelte Gefahr, wenn Sie es später verwenden. Viel besser, es in ein Wenn-Sonst zu wickeln und Topic.none / all für die Randfälle zu verwenden
Ted Pennings
Ein sicherer Weg ist:Topic.where("id NOT IN (?)", actions.presence || [0])
Weston Ganger
0

Hier ist eine komplexere "Nicht-In" -Abfrage, bei der eine Unterabfrage in Schienen 4 mit Squeel verwendet wird. Natürlich sehr langsam im Vergleich zum 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 Operator in Squeel.

Hoffe das hilft jemandem.

dukha
quelle