ActiveRecord / Rails ile bir NOT IN sorgusu nasıl ifade edilir?


207

Sadece bunu güncellemek için birçok insan buna geliyor gibi görünüyor, Rails 4 kullanıyorsanız Trung Lê` ve VinniVidiVicci'nin cevaplarına bakın.

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

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

Ben umarım içermeyen kolay bir çözüm olduğunu umuyorum find_by_sql, eğer o zaman işe yarayacak sanırım.

Bu makaleye başvuran bu makaleyi buldum :

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

ki aynı

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

Bunun gibi bir yol olup olmadığını merak ediyorum NOT IN:

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

3
Bir FYI olarak, Datamapper'ın NOT IN için özel desteği vardır. Örnek:Person.all(:name.not => ['bob','rick','steve'])
Mark Thomas

1
cahil olduğum için üzgünüm, ama Datamapper nedir? rayların 3 parçası mı?
Toby Joiner

2
veri eşleyici, veri depolamanın alternatif bir yoludur, Active Record'u farklı bir yapı ile değiştirir ve ardından sorgular gibi modelinizle ilgili öğeleri farklı şekilde yazarsınız.
Michael Durrant

Yanıtlar:


313

Raylar 4+:

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

Raylar 3:

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

Nerede actionsbir dizi bulunur:[1,2,3,4,5]


1
En son Aktif Kayıt sorgulama modeli
Nevir

5
@ NewAlexandria haklı, bu yüzden böyle bir şey yapmanız gerekecek Topic.where('id NOT IN (?)', (actions.empty? ? '', actions). Yine de nil üzerinde kırılacaktı, ancak geçirdiğiniz dizinin genellikle []en azından geri dönecek ve asla sıfır olacak bir filtre tarafından oluşturulduğunu görüyorum . Aktif Kaydın üstünde bir DSL olan Squeel'e göz atmanızı tavsiye ederim. Sonra şunları yapabilirsiniz:, Topic.where{id.not_in actions}nil / empty / veya başka türlü.
danneu

6
@danneu sadece takas .empty?için .blank?ve nil geçirmez
colllin

@daaneu tarafından (actions.empty?? '', actions) olmalıdır (actions.empty?? '': actions)
marcel salathe

3
raylar 4 notasyonu için gidin: Article.where.not (başlık: ['Rails 3', 'Rails 5'])
Tal

152

FYI, Rails 4'te notsözdizimini kullanabilirsiniz :

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

11
en sonunda! onları dahil etmek için bu kadar uzun süren ne oldu? :)
Dominik Goltermann

50

Şöyle bir şey deneyebilirsiniz:

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

Yapmanız gerekebilir @forums.map(&:id).join(','). Rails, eğer sayılabilirse, bir CSV listesine argüman olup olmayacağını hatırlayamıyorum.

Bunu da yapabilirsiniz:

# 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 kullanarak:

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

veya tercih edilirse:

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

ve 4 numaralı raylardan beri:

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

Sonunda forum_ids'in kimlik listesi olmasını istemediğinizi, bunun yerine bir alt sorguyu istediğinizi lütfen unutmayın, eğer öyleyse konuları almadan önce böyle bir şey yapmanız gerekir:

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

bu şekilde her şeyi tek bir sorguda alırsınız:

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

Ayrıca nihayetinde bunu yapmak istemediğinizi, daha ziyade bir birleştirmeyi - daha verimli olabileceğini unutmayın.


2
Birleştirme daha verimli olabilir , ancak zorunlu olmayabilir. Kullandığınızdan emin olun EXPLAIN!
James

20

@Trung Lê cevabını genişletmek için Rails 4'te aşağıdakileri yapabilirsiniz:

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

Ve bir adım daha ileri gidebilirsiniz. Önce yalnızca yayınlanan Konuları filtrelemeniz ve ardından istemediğiniz kimlikleri filtrelemeniz gerekiyorsa, bunu yapabilirsiniz:

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

Rails 4 çok daha kolay!


12

Kabul edilen çözüm @forumsboşsa başarısız olur . Bu sorunu çözmek için yapmam gerekiyordu

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

Veya Rails 3+ kullanıyorsanız:

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

4

Yukarıdaki cevapların çoğu size yeterlidir, ancak bu tür yüklem ve karmaşık kombinasyonlardan çok daha fazlasını yapıyorsanız, Squeel'i kontrol edin . Şöyle bir şey yapabileceksiniz:

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

Orijinal yazı özellikle sayısal kimlikler kullanarak bahseder, ancak buraya dizeleri bir dizi ile bir IN IN yapmak için sözdizimi aramaya geldi.

ActiveRecord bunu sizin için de güzelce halledecektir:

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

1

Bu forum kimlikleri pragmatik bir şekilde çözülebilir mi? örneğin bu forumları bir şekilde bulabilir misiniz - bu durumda böyle bir şey yapmalısınız

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

Bir SQL yapmaktan daha verimli olurdu not in


1

Bu şekilde okunabilirliği optimize eder, ancak veritabanı sorguları açısından o kadar verimli değildir:

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

0

Sql'yi koşullarınızda kullanabilirsiniz:

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


0

Boş bir diziyi sorguladığınızda, "NULL" döndürmemesi ve sorguyu kırmaması için where blokundaki diziye "<< 0" ekleyin.

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

İşlemler boş veya boş bir dizi olabilir.


1
Uyarı: Bu aslında diziye 0 ekler, bu yüzden artık boş değildir. Ayrıca diziyi değiştirmenin yan etkisi vardır - daha sonra kullanırsanız çift tehlike. Bir if-else içine sarmak ve kenar vakalar için Topic.none / all kullanmak çok daha iyi
Ted Pennings

Daha güvenli bir yol:Topic.where("id NOT IN (?)", actions.presence || [0])
Weston Ganger

0

İşte squeel kullanarak raylar 4 bir alt sorgu kullanarak, daha karmaşık bir "değil" sorgusu. Tabii ki eşdeğer sql ile karşılaştırıldığında çok yavaş, ama hey, işe yarıyor.

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

Kapsamdaki ilk 2 yöntem, cavtl1 ve tl1 takma adlarını bildiren diğer kapsamlardır. << squeel içindeki operatörde değildir.

Umarım bu birine yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.