จะแสดงข้อความค้นหา NOT IN ด้วย ActiveRecord / Rails ได้อย่างไร


207

เพียงแค่อัปเดตสิ่งนี้เพราะดูเหมือนว่ามีผู้คนมากมายมาที่นี่หากคุณใช้ Rails 4 ดูคำตอบของ Trung Lê`และ 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
ในฐานะที่เป็น FYI Datamapper ได้รับการสนับสนุนเป็นพิเศษสำหรับ NOT IN ตัวอย่าง:Person.all(:name.not => ['bob','rick','steve'])
Mark Thomas

1
ขออภัยที่ไม่รู้อะไร แต่ Datamapper คืออะไร เป็นส่วนหนึ่งของราง 3 ไหม
Toby Joiner

2
data mapper เป็นอีกทางเลือกหนึ่งในการจัดเก็บข้อมูลมันจะแทนที่ Active Record ด้วยโครงสร้างที่แตกต่างกันและจากนั้นคุณก็เขียนสิ่งต่าง ๆ ที่เกี่ยวข้องกับโมเดลของคุณเช่นแบบสอบถามต่างๆ
Michael Durrant

คำตอบ:


313

Rails 4+:

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

ราง 3:

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

actionsอาร์เรย์อยู่ที่ไหน:[1,2,3,4,5]


1
นี่เป็นวิธีการที่เหมาะสมกับรูปแบบการสืบค้นล่าสุดของ Active Record
Nevir

5
@NewAlexandria Topic.where('id NOT IN (?)', (actions.empty? ? '', actions)ถูกต้องเพื่อให้คุณจะต้องทำสิ่งที่ชอบ มันจะยังคงพังอยู่ แต่ฉันพบว่าอาเรย์ที่คุณผ่านนั้นมักจะถูกสร้างขึ้นโดยตัวกรองที่จะกลับมา[]อย่างน้อยที่สุดและไม่เคยเป็นศูนย์เลย ฉันขอแนะนำให้ตรวจสอบ Squeel, DSL ด้านบนของ Active Record จากนั้นคุณสามารถทำได้: Topic.where{id.not_in actions}, ไม่มี / ว่าง / หรืออย่างอื่น
danneu

6
@danneu เพียงแลกเปลี่ยน.empty?สำหรับ.blank?และคุณจะไม่มีหลักฐาน
colllin

(actions.empty? '', การกระทำ) โดย @daaneu ควรเป็น (actions.empty?? '': actions)
marcel salathe

3
ไปสำหรับสัญกรณ์รถไฟ 4: Article.where.not (ชื่อ: ['Rails 3', 'Rails 5'])
Tal

152

FYI ใน Rails 4 คุณสามารถใช้notไวยากรณ์:

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

11
ในที่สุด! สิ่งที่ใช้เวลานานมากในการรวมสิ่งนั้น :)
Dominik Goltermann

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 เป็นรายการ 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*/)

โปรดสังเกตด้วยว่าในที่สุดคุณไม่ต้องการทำสิ่งนี้ แต่เป็นการเข้าร่วมสิ่งที่อาจมีประสิทธิภาพมากขึ้น


2
การเข้าร่วมอาจมีประสิทธิภาพมากขึ้น แต่ไม่จำเป็นเสมอไป อย่าลืมใช้EXPLAIN!
James

20

หากต้องการขยายคำตอบ @Trung Lêใน Rails 4 คุณสามารถทำสิ่งต่อไปนี้:

Topic.where.not(forum_id:@forums.map(&: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

คุณอาจต้องดูที่ปลั๊กอิน meta_whereโดย Ernie Miller คำสั่ง SQL ของคุณ:

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

... สามารถแสดงได้ดังนี้:

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

ไรอันเบตส์ของ Railscasts สร้างscreencast ดีอธิบาย MetaWhere

ไม่แน่ใจว่านี่คือสิ่งที่คุณกำลังมองหา แต่สำหรับฉันแล้วมันดูดีกว่าการสืบค้น SQL แบบฝังตัวแน่นอน


2

โพสต์ต้นฉบับกล่าวถึงเป็นพิเศษโดยใช้รหัสตัวเลข แต่ฉันมาที่นี่เพื่อค้นหาไวยากรณ์สำหรับการทำ NOT IN ด้วยอาร์เรย์ของสตริง

ActiveRecord จะจัดการกับคุณเช่นกัน:

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

1

รหัสฟอรัมเหล่านี้สามารถใช้งานได้จริงหรือไม่? เช่นคุณสามารถค้นหาฟอรัมเหล่านี้ได้หรือไม่ - ถ้าเป็นเช่นนั้นคุณควรทำอะไรเช่นนี้

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

เมื่อคุณสอบถามอาเรย์ที่ว่างเปล่าให้เพิ่ม "<< 0" ลงในอาเรย์ในตำแหน่งที่บล็อกดังนั้นมันจะไม่ส่งกลับค่า "NULL" และทำลายเคียวรี

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

หากการกระทำอาจเป็นอาเรย์ที่ว่างหรือว่างเปล่า


1
คำเตือน: สิ่งนี้จะเพิ่ม 0 เข้าไปในอาร์เรย์ดังนั้นจึงไม่มีความว่างเปล่าอีกต่อไป นอกจากนี้ยังมีผลข้างเคียงของการแก้ไขอาเรย์ - อันตรายสองเท่าหากคุณใช้ในภายหลัง ดีกว่าที่จะห่อใน if-else และใช้ Topic.none / ทั้งหมดสำหรับกรณีขอบ
Ted Pennings

วิธีที่ปลอดภัยกว่าคือ:Topic.where("id NOT IN (?)", actions.presence || [0])
Weston Ganger

0

ต่อไปนี้เป็นแบบสอบถามแบบ "ไม่ได้อยู่ใน" ที่ซับซ้อนยิ่งขึ้นโดยใช้แบบสอบถามย่อยใน Rails 4 โดยใช้ squeel แน่นอนว่าช้ามากเมื่อเทียบกับ 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 วิธีแรกในขอบเขตคือขอบเขตอื่นที่ประกาศ alias cavtl1 และ tl1 << ไม่ใช่ตัวดำเนินการที่อยู่ใน squeel

หวังว่านี่จะช่วยใครซักคน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.