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


207

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

3
参考までに、DatamapperはNOT INを具体的にサポートしています。例:Person.all(:name.not => ['bob','rick','steve'])
マークトーマス

1
無知で申し訳ありませんが、Datamapperとは何ですか?その部分はレール3ですか?
トビージョイナー

2
データマッパーは、データを保存する別の方法です。ActiveRecordを別の構造に置き換え、クエリなどのモデル関連のものを別の方法で記述します。
マイケルデュラント

回答:


313

Rails 4以降:

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

Rails 3:

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

actions配列はどこにありますか:[1,2,3,4,5]


1
これは、最新のアクティブレコードクエリモデルでの適切なアプローチです
Nevir

5
@NewAlexandriaは正しいので、のようにする必要がありますTopic.where('id NOT IN (?)', (actions.empty? ? '', actions)。それでもnilで壊れますが、渡した配列は通常[]、最低でもnil を返さないフィルターによって生成されることがわかります。Active Recordの上にあるDSL、Squeelをチェックすることをお勧めします。次に、次のようにできます:Topic.where{id.not_in actions}、nil / empty /その他。
danneu 2013年

6
@danneuは交換.empty?するだけ.blank?であなたは
無防備です

(actions.empty?? ''、actions)@daaneuは(actions.empty?? '':actions)
marcel salathe

3
rails 4表記に移動:Article.where.not(title:['Rails 3'、 'Rails 5'])
Tal

152

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

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

11
最後に!それを含めるのに何がそんなに時間がかかったのですか?:)
Dominik Goltermann 2013年

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

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)

そしてレール4以降:

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

最終的にforum_idsをidsリストではなくサブクエリにしたいことに注意してください。そうであれば、トピックを取得する前に次のようなことを行う必要があります。

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

このようにして、すべてを1つのクエリで取得します。

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

また、最終的にはこれを実行するのではなく、結合を実行する必要があることにも注意してください。


2
結合の方が効率的かもしれませんが、必ずしもそうではありません。必ず使用してくださいEXPLAIN
James

20

@TrungLêの回答を拡張するには、Rails 4で次のようにします。

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

そして、あなたはそれをさらに一歩進めることができます。最初に公開されたトピックのみをフィルタリングする必要があり、次に不要なIDを除外する必要がある場合は、次のようにします。

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

Rails 4はそれをとても簡単にします!


12

@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

4

上記の答えのほとんどで十分ですが、このような述語と複雑な組み合わせをさらに実行している場合は、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)}

2

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の使用について具体的に言及していますが、文字列の配列でNOT INを実行するための構文を探してここに来ました。

ActiveRecordもそれをうまく処理します。

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

1

これらのフォーラム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

この方法は可読性を最適化しますが、データベースクエリに関しては効率的ではありません。

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

0

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

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


0

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

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

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


1
警告:これは実際には配列に0を追加するため、空ではなくなります。また、配列を変更するという副作用もあります-後で使用すると二重の危険があります。if-elseでラップし、エッジケースにはTopic.none / allを使用する方がずっと良い
Ted Pennings

より安全な方法がある:Topic.where("id NOT IN (?)", actions.presence || [0])
ウェストンGanger

0

以下は、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演算子です。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.