Rails ที่เงื่อนไขใช้ NOT NIL


359

ใช้สไตล์ 3 รางฉันจะเขียนสิ่งที่ตรงกันข้ามอย่างไร:

Foo.includes(:bar).where(:bars=>{:id=>nil})

ฉันต้องการหาที่ id ไม่ได้เป็นศูนย์ ฉันเหนื่อย:

Foo.includes(:bar).where(:bars=>{:id=>!nil}).to_sql

แต่มันกลับมา:

=> "SELECT     \"foos\".* FROM       \"foos\"  WHERE  (\"bars\".\"id\" = 1)"

นั่นไม่ใช่สิ่งที่ฉันต้องการแน่นอนและเกือบจะดูเหมือนเป็นข้อผิดพลาดใน AREL


2
!nilประเมินtrueในรูบีและ Arel แปลtrueไป1ในแบบสอบถาม SQL ดังนั้นข้อความค้นหาที่สร้างขึ้นจึงเป็นสิ่งที่คุณขอมา - นี่ไม่ใช่ข้อผิดพลาด ARel
yuval

คำตอบ:


510

วิธีการยอมรับของ Rails 3:

Foo.includes(:bar).where("bars.id IS NOT NULL")

ActiveRecord 4.0 และสูงกว่าเพิ่มwhere.notเพื่อให้คุณสามารถทำสิ่งนี้:

Foo.includes(:bar).where.not('bars.id' => nil)
Foo.includes(:bar).where.not(bars: { id: nil })

เมื่อทำงานกับขอบเขตระหว่างตารางฉันต้องการยกระดับmergeเพื่อให้ฉันสามารถใช้ขอบเขตที่มีอยู่ได้ง่ายขึ้น

Foo.includes(:bar).merge(Bar.where.not(id: nil))

นอกจากนี้เนื่องจากincludesไม่ได้เลือกกลยุทธ์การเข้าร่วมเสมอคุณควรใช้referencesที่นี่เช่นกันมิฉะนั้นคุณอาจสิ้นสุดด้วย SQL ที่ไม่ถูกต้อง

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))

1
อันสุดท้ายที่นี่ไม่ทำงานสำหรับฉันเราต้องการอัญมณีหรือปลั๊กอินพิเศษสำหรับสิ่งนี้หรือไม่? ฉันได้รับ: rails undefined method 'not_eq' for :confirmed_at:Symbol..
ทิม Baas

3
@Tim ใช่แล้ว MetaWhere gem ที่ฉันลิงค์ไว้ด้านบน
Adam Lassek

3
ฉันชอบวิธีแก้ปัญหาที่ไม่ต้องใช้อัญมณีอื่น ๆ :) แม้ว่ามันจะดูน่าเกลียด
ก็ตาม

1
@oreoshake MetaWhere / Squeel คุ้มค่าที่จะมีนี่เป็นเพียงแง่มุมเล็ก ๆ แต่แน่นอนเป็นกรณีทั่วไปที่ดีที่จะรู้
Adam Lassek

1
@BKSpurgeon Chaining whereเงื่อนไขเป็นเพียงการสร้าง AST ก็ไม่ได้ตีฐานข้อมูลจนกว่าคุณจะตีเป็นวิธีการที่เทอร์มิชอบหรือeach to_aการสร้างแบบสอบถามไม่ได้เกี่ยวข้องกับประสิทธิภาพ สิ่งที่คุณขอจากฐานข้อมูลคือ
Adam Lassek

251

ไม่ใช่ข้อผิดพลาดใน AREL แต่เป็นข้อบกพร่องในตรรกะของคุณ

สิ่งที่คุณต้องการคือ:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))

2
ฉันอยากรู้ว่าตรรกะคืออะไรสำหรับการเปลี่ยน! ไม่มีเป็น '1'
SooDesuNe

12
เมื่อเดาแล้ว! nil จะคืนกลับtrueซึ่งเป็นบูลีน :id => trueรับคุณid = 1ใน SQLese
zetetic

นี่เป็นวิธีที่ดีในการหลีกเลี่ยงการเขียนชิ้นส่วน SQL ดิบ ไวยากรณ์ไม่กระชับเท่า Squeel
เคลวิน

1
ฉันไม่ได้จัดการกับ sqlite3 sqlite3 field_name != 'NULL'ต้องการที่จะเห็น
mjnissim

@zetetic หากคุณไม่ได้ใช้ postgres ซึ่งในกรณีนี้คุณจะได้รับid = 't':)
thekingoftruth

36

สำหรับ Rails4:

ดังนั้นสิ่งที่คุณต้องการคือการเข้าร่วมภายในดังนั้นคุณควรใช้การรวมคำกริยา:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

แต่สำหรับบันทึกถ้าคุณต้องการเงื่อนไข "ไม่เป็นโมฆะ" เพียงแค่ใช้คำกริยาไม่:

Foo.includes(:bar).where.not(bars: {id: nil})

Select * from Foo Left Outer Join Bars on .. WHERE bars.id IS NOT NULL

โปรดทราบว่าไวยากรณ์นี้รายงานการคัดค้าน (มันพูดถึงสตริงข้อมูล SQL แต่ฉันเดาว่าเงื่อนไขแฮชถูกเปลี่ยนเป็นสตริงในตัวแยกวิเคราะห์?) ดังนั้นโปรดเพิ่มการอ้างอิงไปยังจุดสิ้นสุด:

Foo.includes(:bar).where.not(bars: {id: nil}).references(:bar)

คำเตือนการเลิกใช้งาน: ดูเหมือนว่าคุณกำลังโหลดตาราง (หนึ่งใน: .... ) ที่อ้างถึงในตัวอย่าง SQL ของสตริง ตัวอย่างเช่น:

Post.includes(:comments).where("comments.title = 'foo'")

ขณะนี้ Active Record รับรู้ตารางในสตริงและรู้ว่าจะเข้าร่วมตารางความคิดเห็นในแบบสอบถามแทนที่จะโหลดความคิดเห็นในแบบสอบถามแยกต่างหาก อย่างไรก็ตามการทำเช่นนี้โดยไม่ได้เขียนตัวแยกวิเคราะห์ SQL แบบเต็มเป่ามีข้อบกพร่องโดยเนื้อแท้ เนื่องจากเราไม่ต้องการเขียนตัวแยกวิเคราะห์ SQL เราจึงลบฟังก์ชันการทำงานนี้ จากนี้ไปคุณต้องบอก Active Record อย่างชัดเจนเมื่อคุณอ้างถึงตารางจากสตริง:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)

1
การreferencesโทรช่วยฉัน!
theblang


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