วิธีเชื่อมโยงการสืบค้นขอบเขตด้วยหรือแทนที่จะเป็น AND


140

ฉันใช้ Rails3, ActiveRecord

แค่สงสัยว่าฉันจะเชื่อมโยงขอบเขตด้วยคำสั่ง OR แทนที่จะเป็น AND ได้อย่างไร

เช่น

Person.where(:name => "John").where(:lastname => "Smith")

โดยปกติจะส่งคืน:

name = 'John' AND lastname = 'Smith'

แต่ฉันต้องการ:

`name = 'John' OR lastname = 'Smith'

คำตอบ:


109

คุณจะทำ

Person.where('name=? OR lastname=?', 'John', 'Smith')

ตอนนี้ไม่มีสิ่งอื่นใดหรือการสนับสนุนจากไวยากรณ์ AR3 ใหม่ (ซึ่งไม่ได้ใช้อัญมณีของบุคคลที่สาม)


22
และเพื่อความปลอดภัยคุณควรแสดงสิ่งนี้ในฐานะ Person.where ("name =? OR lastname =?", 'John', 'Smith')
CambridgeMike

6
สิ่งนี้ยังใช้ได้ใน Rails 4? ไม่มีอะไรดีขึ้นใน Rails 4?
Donato

11
ไม่มีการเปลี่ยนแปลงใน Rails 4 แต่ Rails 5 จะมี.orในตัว
มะนาว

3
นี่ไม่ใช่ขอบเขตเป็นเพียง 2 ที่อนุประโยคหรือกรณีขอบเขตที่เฉพาะเจาะจงมาก จะเป็นอย่างไรหากฉันต้องการเชื่อมโยงขอบเขตที่ซับซ้อนมากขึ้นด้วยการรวม
miguelfg

2
ด้วย Rails 5 คุณสามารถทำบางสิ่งเช่น Person.where ("name = 'John'") หรือ (Person.where ("lastname = 'Smith'"))
shyam


48

ใช้ARel

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)

1
ใครสามารถแสดงความคิดเห็นว่าแบบสอบถามประเภทนี้แตกใน Postgres หรือไม่?
Olivier Lacan

ARel ควรจะเป็น DB-agnostic
Simon Perepelitsa

แม้ว่าสิ่งนี้จะดูเหมือนเป็นความคิดที่ดี แต่ ARel ไม่ใช่ API สาธารณะและการใช้สิ่งนี้อาจทำให้แอปของคุณเสียหายด้วยวิธีที่ไม่คาดคิดดังนั้นฉันขอแนะนำให้ดำเนินการดังกล่าวเว้นแต่คุณจะให้ความสนใจอย่างใกล้ชิดกับวิวัฒนาการของ ARel API
Olivier Lacan

7
@OlivierLacan Arel เป็น API สาธารณะหรืออย่างแม่นยำมากขึ้นก็เปิดเผยอย่างใดอย่างหนึ่ง Active Record เป็นเพียงวิธีการอำนวยความสะดวกที่ใช้ภายใต้ประทุนมันถูกต้องสมบูรณ์ที่จะใช้มันด้วยตัวเอง เป็นไปตามการกำหนดเวอร์ชันเชิงความหมายดังนั้นหากคุณไม่เปลี่ยนเวอร์ชันหลัก (3.xx => 4.xx) ก็ไม่จำเป็นต้องกังวลเกี่ยวกับการเปลี่ยนแปลงที่ไม่สมบูรณ์
สีเทา

45

เพียงแค่โพสต์ไวยากรณ์ Array สำหรับคอลัมน์เดียวกันหรือแบบสอบถามเพื่อช่วยในการเปิดเผย

Person.where(name: ["John", "Steve"])

2
คำถามตรวจสอบ John เทียบกับชื่อและ Smith เทียบกับนามสกุล
steven_noble

14
@steven_noble - นั่นคือเหตุผลที่ฉันทำให้ "คอลัมน์เดียวกัน" เป็นตัวหนาพร้อมกับหรือข้อความค้นหา ฉันสามารถลบความคิดเห็นทั้งหมดได้ แต่คิดว่ามันจะมีประโยชน์สำหรับคนที่อ่าน "หรือ" ถามคำถาม SO
daino3

38

อัปเดตสำหรับ Rails4

ไม่ต้องใช้อัญมณีของบุคคลที่สาม

a = Person.where(name: "John") # or any scope 
b = Person.where(lastname: "Smith") # or any scope 
Person.where([a, b].map{|s| s.arel.constraints.reduce(:and) }.reduce(:or))\
  .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }

คำตอบเก่า

ไม่ต้องใช้อัญมณีของบุคคลที่สาม

Person.where(
    Person.where(:name => "John").where(:lastname => "Smith")
      .where_values.reduce(:or)
)

5
นี่เป็นคำตอบที่แท้จริงเพียงข้อเดียวสำหรับคำถามของ OP ในแง่ของทรัพยากรและวิธีการ คำตอบอื่น ๆ ได้แก่ (a) ต้องการ apis ของบุคคลที่สามหรือ (b) ต้องการการแก้ไขorหรือตัวดำเนินการอื่น ๆ ภายในบล็อกข้อความซึ่งทั้งสองข้อนี้จะไม่แสดงในรหัสของ OP
allenwlee

6
นี่คือบทความที่ดีที่อธิบายว่าcoderwall.com/p/dgv7ag/…
allenwlee

4
ใช้...where_values.join(' OR ')ในกรณีของฉัน
Sash

2
คุณสามารถละเว้น.tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }ส่วนนี้ได้หากขอบเขตของคุณไม่มีตัวแปรใด ๆ ที่จำเป็นต้องมีการเชื่อมโยง
fatuhoku

23

ในกรณีที่คนที่กำลังมองหาคำตอบการปรับปรุงเพื่อให้คนนี้ก็ดูเหมือนว่ามีการร้องขอดึงที่มีอยู่จะได้รับนี้ลงไปในราง: https://github.com/rails/rails/pull/9052

ขอบคุณแพทช์ลิงของ @ j-mcnally สำหรับ ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) คุณสามารถทำสิ่งต่อไปนี้:

Person.where(name: 'John').or.where(last_name: 'Smith').all

สิ่งที่มีค่ายิ่งกว่าคือความสามารถในการเชื่อมโยงขอบเขตด้วยOR:

scope :first_or_last_name, ->(name) { where(name: name.split(' ').first).or.where(last_name: name.split(' ').last) }
scope :parent_last_name, ->(name) { includes(:parents).where(last_name: name) }

จากนั้นคุณสามารถค้นหาบุคคลทั้งหมดที่มีชื่อหรือนามสกุลหรือมีผู้ปกครองที่มีนามสกุล

Person.first_or_last_name('John Smith').or.parent_last_name('Smith')

ไม่ใช่ตัวอย่างที่ดีที่สุดสำหรับการใช้สิ่งนี้ แต่เพียงพยายามทำให้เหมาะสมกับคำถาม


2
คุณต้องการรุ่นหรือรางใดสำหรับสิ่งนี้
Sash

3
การสนทนาล่าสุดและคำขอดึงที่จะทำให้สิ่งนี้กลายเป็น Rails core สามารถพบได้ที่นี่: github.com/rails/rails/pull/16052 Rails 5.0 มีเป้าหมายสำหรับการเปิดตัว.orฟังก์ชัน chain อย่างเป็นทางการ ฉันสามารถใช้แพทช์ลิงที่ฉันพูดถึงใน Rails> = 3.2; อย่างไรก็ตามคุณอาจต้องรอให้ Rails 5 แนะนำการเปลี่ยนแปลงที่มีมายาวนานกับโค้ดของคุณ
codenamev

1
ใช่มันถูกรวมเข้าด้วยกันและปล่อยออกมากับ Rails 5
amoebe



8

นี่จะเป็นตัวเลือกที่ดีสำหรับ MetaWhere ถ้าคุณใช้ Rails 3.0+ แต่ไม่สามารถใช้กับ Rails 3.1 คุณอาจต้องการลองรับสารภาพแทน สร้างโดยผู้เขียนคนเดียวกัน นี่คือวิธีที่คุณจะดำเนินการตามโซ่หรือตาม:

Person.where{(name == "John") | (lastname == "Smith")}

คุณสามารถผสมและจับคู่ AND / OR ท่ามกลางสิ่งที่ยอดเยี่ยมอื่น ๆ อีกมากมาย


8

Rails / ActiveRecord เวอร์ชันอัปเดตอาจรองรับไวยากรณ์นี้โดยกำเนิด มันจะดูคล้ายกับ:

Foo.where(foo: 'bar').or.where(bar: 'bar')

ตามที่ระบุไว้ในคำขอดึงนี้https://github.com/rails/rails/pull/9052

สำหรับตอนนี้เพียงแค่ยึดติดกับผลงานต่อไปนี้:

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')

5
ไม่รองรับใน Rails 4.2.5
fatuhoku

1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')ไม่สำเร็จ. ควรจะเป็น Foo ที่ไหน (foo: 'bar1') หรือที่ไหน (Foo.where (bar: 'bar2'))
Ana MaríaMartínezGómez

6

Rails 4+ ขอบเขต + Arel

class Creature < ActiveRecord::Base
    scope :is_good_pet, -> {
        where(
            arel_table[:is_cat].eq(true)
            .or(arel_table[:is_dog].eq(true))
            .or(arel_table[:eats_children].eq(false))
        )
    }
end

ฉันลองเชื่อมโยงขอบเขตที่มีชื่อด้วย. หรือไม่มีโชค แต่สิ่งนี้ใช้ได้ผลกับการค้นหาทุกอย่างด้วยชุดบูลีนเหล่านั้น สร้าง SQL เช่น

SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))

6

หากคุณต้องการให้ขอบเขต (แทนที่จะทำงานอย่างชัดเจนกับชุดข้อมูลทั้งหมด) นี่คือสิ่งที่คุณควรทำกับ Rails 5:

scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }

หรือ:

def self.john_or_smith
  where(name: "John").or(where(lastname: "Smith"))
end

สิ่งนี้ถูกต้อง แต่เป็นคำตอบที่เป็นตัวอักษรมากกว่าสิ่งเดียวกับที่stackoverflow.com/a/42894753/1032882ทำในทางเทคนิค
RudyOnRails


3

หากคุณไม่สามารถเขียน where clause ด้วยตนเองเพื่อรวมคำสั่ง "หรือ" (กล่าวคือคุณต้องการรวมสองขอบเขต) คุณสามารถใช้ union:

Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")

(ที่มา: ActiveRecord Query Union )

นี่จะส่งคืนระเบียนทั้งหมดที่ตรงกับข้อความค้นหา อย่างไรก็ตามสิ่งนี้ส่งคืนอาร์เรย์ไม่ใช่ arel ถ้าคุณอยากจะกลับ Arel คุณเช็คเอากระทู้นี้: https://gist.github.com/j-mcnally/250eaaceef234dd8971b

สิ่งนี้จะได้ผลตราบเท่าที่คุณไม่รังเกียจรางปะลิง


2

ยังเห็นคำถามที่เกี่ยวข้องเหล่านี้: ที่นี่ , ที่นี่และที่นี่

สำหรับราง 4 อ้างอิงจากบทความนี้และคำตอบเดิมนี้

Person
  .unscoped # See the caution note below. Maybe you want default scope here, in which case just remove this line.
  .where( # Begin a where clause
    where(:name => "John").where(:lastname => "Smith")  # join the scopes to be OR'd
    .where_values  # get an array of arel where clause conditions based on the chain thus far
    .inject(:or)  # inject the OR operator into the arels 
    # ^^ Inject may not work in Rails3. But this should work instead:
    .joins(" OR ")
    # ^^ Remember to only use .inject or .joins, not both
  )  # Resurface the arels inside the overarching query

หมายเหตุข้อควรระวังของบทความในตอนท้าย:

ราง 4.1+

Rails 4.1 ถือว่า default_scope เหมือนกับขอบเขตทั่วไป ขอบเขตเริ่มต้น (ถ้าคุณมี) จะรวมอยู่ในผลลัพธ์ where_values ​​และการแทรก (: หรือ) จะเพิ่มหรือคำสั่งระหว่างขอบเขตเริ่มต้นและส่วนของคุณ เลวร้าย.

ในการแก้ปัญหานั้นคุณเพียงแค่ต้องเปิดใช้งานการค้นหา


1

นี่เป็นวิธีที่สะดวกมากและใช้งานได้ดีใน Rails 5:

Transaction
  .where(transaction_type: ["Create", "Correspond"])
  .or(
    Transaction.where(
      transaction_type: "Status",
      field: "Status",
      newvalue: ["resolved", "deleted"]
    )
  )
  .or(
    Transaction.where(transaction_type: "Set", field: "Queue")
  )

0

squeelอัญมณีมีวิธีที่ง่ายอย่างไม่น่าเชื่อที่จะบรรลุนี้ (ก่อนที่จะมีอะไรบางอย่างที่ผมใช้วิธีการเช่นนี้ของ @ coloradoblue):

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}

0

ดังนั้นคำตอบสำหรับคำถามเดิมคุณสามารถเข้าร่วมขอบเขตด้วย 'หรือ' แทน 'และ' ดูเหมือนว่าจะเป็น "ไม่คุณทำไม่ได้" แต่คุณสามารถเขียนโค้ดขอบเขตหรือคิวรีที่แตกต่างไปจากเดิมอย่างสิ้นเชิงที่ทำงานหรือใช้กรอบงานที่แตกต่างจาก ActiveRecord เช่น MetaWhere หรือ Squeel ไม่มีประโยชน์ในกรณีของฉัน

ฉันกำลัง 'หรือ' ขอบเขตที่สร้างขึ้นโดย pg_search ซึ่งทำมากกว่าการเลือกเล็กน้อยซึ่งรวมถึงคำสั่งโดย ASC ซึ่งทำให้สหภาพสะอาด ฉันต้องการ 'หรือ' ด้วยขอบเขตงานฝีมือที่ทำในสิ่งที่ฉันไม่สามารถทำได้ใน pg_search ผมต้องทำแบบนี้

Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")

คือเปลี่ยนขอบเขตเป็น sql ใส่วงเล็บรอบ ๆ แต่ละอันรวมเข้าด้วยกันแล้ว find_by_sql โดยใช้ sql ที่สร้างขึ้น เป็นขยะเล็กน้อย แต่ก็ใช้ได้ผล

ไม่อย่าบอกฉันว่าฉันสามารถใช้ "against: [: name,: code]" ใน pg_search ได้ฉันอยากทำแบบนั้น แต่ฟิลด์ "name" เป็น hstore ซึ่ง pg_search ไม่สามารถจัดการได้ ยัง. ดังนั้นขอบเขตตามชื่อจะต้องถูกสร้างขึ้นด้วยมือจากนั้นรวมเข้ากับขอบเขต pg_search


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