web-dev-qa-db-ja.com

ActiveRecord / RailsでNOT INクエリを表現する方法は?

Rails 4を使用している場合、多くの人々がこれに来るように見えるので、これを更新するために、TrungLê`とVinniVidiVicciの回答を見てください。

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

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

find_by_sqlを含まない簡単な解決策があることを望んでいます。

これを参照する この記事 を見つけました。

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

と同じです

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

それでNOT INを行う方法があるかどうか疑問に思っています:

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

私はこれを使用しています:

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

ここで、actionsは次の配列です:[1,2,3,4,5]

編集:

Rails 4表記の場合:

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

参考までに、Rails 4では、not構文を使用できます。

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

Arelの使用:

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

または、必要に応じて:

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

Rails 4以降:

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

最終的に、フォーラムIDをIDリストではなくサブクエリにしたい場合は、トピックを取得する前に次のようにする必要があることに注意してください。

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

このようにして、すべてを単一のクエリで取得できます。

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

また、最終的にはこれを行いたくないが、むしろ結合することに注意してください-より効率的かもしれません

50

次のようなものを試すことができます:

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

@forums.map(&:id).join(',')を実行する必要があるかもしれません。列挙可能な場合、Railsが引数をCSVリストに含めるかどうかは思い出せません。

これを行うこともできます:

# 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

@TrungLêの回答を展開するには、Rails 4で次のことができます。

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

そして、さらに一歩踏み出すことができます。最初に公開されたトピックのみをフィルタリングし、不要なIDをthenでフィルタリングする必要がある場合、これを行うことができます。

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

Rails 4を使用すると、非常に簡単になります。

17
VinniVidiVicci

@forumsが空の場合、受け入れられたソリューションは失敗します。これを回避するには、私がしなければなりませんでした

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

または、Rails 3+を使用している場合:

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

上記の答えの大部分で十分ですが、このような述語と複雑な組み合わせをさらに多く行う場合は、 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)}
4
jake

Ernie Millerによる meta_whereプラグイン をご覧ください。 SQLステートメント:

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

...このように表現できます:

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

RailscastsのRyan Batesが MetaWhereを説明する素敵なスクリーンキャスト を作成しました。

これがあなたが探しているものかどうかはわかりませんが、私の目には確かに埋め込みSQLクエリよりも良く見えます。

2

これらのフォーラムIDは、実用的な方法で解決できますか?例えばどういうわけかこれらのフォーラムを見つけることができます-もしそうなら、あなたは次のようなことをすべきです

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

SQL not inを実行するよりも効率的です

1
Omar Qureshi

元の投稿では、数値IDの使用について具体的に言及していますが、文字列の配列でNOT INを実行するための構文を探してここに来ました。

ActiveRecordもあなたのためにそれをうまく処理します:

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

この方法は読みやすさのために最適化されますが、データベースクエリに関しては効率的ではありません。

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

条件でsqlを使用できます。

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

Squeelを使用してRails 4のサブクエリを使用する、より複雑な「not in」クエリを次に示します。もちろん、同等のSQLと比較して非常に遅いですが、ちょっと、それは動作します。

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

スコープ内の最初の2つのメソッドは、エイリアスcavtl1およびtl1を宣言する他のスコープです。 <<は、squeelのnot in演算子です。

これが誰かを助けることを願っています。

0
dukha

空の配列をクエリする場合、whereブロックの配列に「<< 0」を追加して、「NULL」を返さないようにしてクエリを中断します。

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

アクションが空または空の配列である可能性がある場合。

0
itsEconomics

Jonniiからの便乗:

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

要素をマッピングするのではなく、pluckを使用する

railsconf 2012 10知らなかったことRailsができること

0
Thomas Wolfe