Bagaimana cara mengekspresikan permintaan NOT IN dengan ActiveRecord / Rails?


207

Hanya untuk memperbarui ini karena tampaknya banyak orang datang ke ini, jika Anda menggunakan Rails 4 lihat jawaban oleh Trung Lê` dan VinniVidiVicci.

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

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

Saya berharap ada solusi mudah yang tidak melibatkan find_by_sql, jika tidak maka saya rasa itu harus berhasil.

Saya menemukan artikel ini yang mereferensikan ini:

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

yang sama dengan

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

Saya bertanya-tanya apakah ada cara untuk melakukan NOT INitu, seperti:

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

3
Sebagai FYI, Datamapper telah memiliki dukungan khusus untuk NOT IN. Contoh:Person.all(:name.not => ['bob','rick','steve'])
Mark Thomas

1
maaf karena tidak tahu apa-apa, tapi apa itu Datamapper? Apakah itu bagian dari rel 3?
Toby Joiner

2
data mapper adalah cara alternatif untuk menyimpan data, itu menggantikan Rekaman Aktif dengan struktur yang berbeda dan kemudian Anda menulis hal-hal terkait model Anda seperti pertanyaan, berbeda.
Michael Durrant

Jawaban:


313

Rails 4+:

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

Rel 3:

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

Di mana actionsarray dengan:[1,2,3,4,5]


1
Ini adalah pendekatan yang tepat dengan model kueri Rekaman Aktif terbaru
Nevir

5
@NewAlexandria benar, jadi Anda harus melakukan sesuatu seperti Topic.where('id NOT IN (?)', (actions.empty? ? '', actions). Itu masih akan rusak pada nil, tapi saya menemukan bahwa array yang Anda lewati biasanya dihasilkan oleh filter yang akan kembali []paling tidak dan tidak pernah nihil. Saya sarankan memeriksa Squeel, sebuah DSL di atas Rekaman Aktif. Maka Anda bisa melakukan Topic.where{id.not_in actions}:, nihil / kosong / atau sebaliknya.
danneu

6
@danneu hanya swap .empty?untuk .blank?dan Anda nol-bukti
colllin

(action.empty?? '', aksi) oleh @daaneu harus (action.empty?? '': aksi)
marcel salathe

3
pergi untuk notasi rails 4: Article.where.not (judul: ['Rails 3', 'Rails 5'])
Tal

152

FYI, Di Rails 4, Anda dapat menggunakan notsintaks:

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

11
akhirnya! apa yang membuat mereka begitu lama memasukkannya? :)
Dominik Goltermann

50

Anda dapat mencoba sesuatu seperti:

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

Anda mungkin perlu melakukannya @forums.map(&:id).join(','). Saya tidak ingat apakah Rails akan argumen ke daftar CSV jika itu enumerable.

Anda juga bisa melakukan ini:

# 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

Menggunakan Arel:

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

atau, jika disukai:

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

dan karena rel 4 pada:

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

Harap perhatikan bahwa pada akhirnya Anda tidak ingin forum_ids menjadi daftar id, melainkan subquery, jika demikian maka Anda harus melakukan sesuatu seperti ini sebelum mendapatkan topik:

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

dengan cara ini Anda mendapatkan semuanya dalam satu permintaan: sesuatu seperti:

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

Perhatikan juga bahwa pada akhirnya Anda tidak ingin melakukan ini, melainkan bergabung - apa yang mungkin lebih efisien.


2
Bergabung mungkin lebih efisien, tetapi tidak harus. Pastikan untuk menggunakan EXPLAIN!
James

20

Untuk memperluas jawaban @ Trung Lê, di Rails 4 Anda dapat melakukan hal berikut:

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

Dan Anda bisa melangkah lebih jauh. Jika Anda perlu memfilter pertama hanya untuk Topik yang dipublikasikan dan kemudian menyaring id yang tidak Anda inginkan, Anda bisa melakukan ini:

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

Rails 4 membuatnya jauh lebih mudah!


12

Solusi yang diterima gagal jika @forumskosong. Untuk mengatasinya, saya harus melakukannya

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

Atau, jika menggunakan Rails 3+:

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

4

Sebagian besar jawaban di atas sudah cukup bagi Anda, tetapi jika Anda melakukan lebih banyak kombinasi predikat dan kompleks seperti itu periksa Squeel . Anda akan dapat melakukan sesuatu seperti:

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

Anda mungkin ingin melihat plugin meta_where oleh Ernie Miller. Pernyataan SQL Anda:

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

... dapat diekspresikan seperti ini:

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

Ryan Bates dari Railscasts menciptakan screencast yang bagus untuk menjelaskan MetaWhere .

Tidak yakin apakah ini yang Anda cari tetapi bagi saya, ini jelas terlihat lebih baik daripada kueri SQL yang disematkan.


2

Posting asli secara khusus menyebutkan menggunakan ID numerik, tapi saya datang ke sini mencari sintaks untuk melakukan NOT IN dengan array string.

ActiveRecord akan menanganinya dengan baik untuk Anda juga:

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

1

Bisakah id forum ini dikerjakan secara pragmatis? misalnya Anda dapat menemukan forum ini entah bagaimana - jika itu masalahnya Anda harus melakukan sesuatu seperti

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

Yang mana akan lebih efisien daripada melakukan SQL not in


1

Cara ini mengoptimalkan keterbacaan, tetapi tidak seefisien dalam hal permintaan basis data:

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

0

Anda dapat menggunakan sql dalam kondisi Anda:

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


0

Ketika Anda query array kosong tambahkan "<< 0" ke array di blok where sehingga tidak mengembalikan "NULL" dan pecahkan query.

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

Jika tindakan bisa berupa array kosong atau kosong.


1
Peringatan: ini sebenarnya menambahkan 0 pada array, jadi tidak lagi kosong. Ini juga memiliki efek samping dari memodifikasi array - bahaya ganda jika Anda menggunakannya nanti. Jauh lebih baik untuk membungkusnya dalam if-else dan menggunakan Topic.none / all untuk kasus tepi
Ted Pennings

Cara yang lebih aman adalah:Topic.where("id NOT IN (?)", actions.presence || [0])
Weston Ganger

0

Berikut adalah query "tidak dalam" yang lebih kompleks, menggunakan subquery di rails 4 menggunakan squeel. Tentu saja sangat lambat dibandingkan dengan sql yang setara, tapi hei, itu berhasil.

    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 metode pertama dalam ruang lingkup adalah ruang lingkup lain yang menyatakan alias cavtl1 dan tl1. << adalah operator yang tidak ada dalam squeel.

Semoga ini bisa membantu seseorang.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.