Bagaimana cara merangkai kueri cakupan dengan ATAU, bukan DAN?


140

Saya menggunakan Rails3, ActiveRecord

Hanya ingin tahu bagaimana cara merangkai cakupan dengan pernyataan ATAU daripada DAN.

misalnya

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

Itu biasanya mengembalikan:

name = 'John' AND lastname = 'Smith'

tapi saya suka:

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

Jawaban:


109

Anda akan melakukannya

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

Saat ini, tidak ada dukungan ATAU lainnya oleh sintaks AR3 baru (tanpa menggunakan permata pihak ketiga).


22
dan demi keamanan, ada baiknya mengungkapkan ini sebagai Person.where ("name =? OR lastname =?", 'John', 'Smith')
CambridgeMike

6
Ini masih berlaku di Rails 4? Tidak ada yang lebih baik di Rails 4?
Donato

11
Tidak ada perubahan pada Rails 4, tetapi Rails 5 sudah .orterpasang .
jeruk nipis

3
ini bukan cakupan, hanya 2 di mana klausa, atau kasus cakupan yang sangat spesifik. Bagaimana jika saya ingin merangkai cakupan yang lebih kompleks, dengan gabungan misalnya.
miguelfg

2
Dengan Rails 5 Anda juga dapat melakukan sesuatu seperti, Person.where ("name = 'John'"). Atau (Person.where ("lastname = 'Smith'"))
shyam


48

Gunakan ARel

t = Person.arel_table

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

1
Adakah yang bisa mengomentari apakah jenis kueri ini rusak di Postgres?
Olivier Lacan

ARel seharusnya agnostik DB.
Simon Perepelitsa

Meskipun ini sepertinya ide yang bagus, ARel bukanlah API publik dan menggunakannya dapat merusak aplikasi Anda dengan cara yang tidak terduga, jadi saya menyarankan untuk tidak melakukannya kecuali Anda memperhatikan evolusi API ARel.
Olivier Lacan

7
@OlivierLacan Arel adalah API publik, atau lebih tepatnya, itu mengeksposnya. Rekaman Aktif hanya menyediakan metode kenyamanan yang menggunakannya di bawah tenda, Ini sepenuhnya valid untuk digunakan sendiri. Ini mengikuti versi semantik, jadi kecuali Anda mengubah versi utama (3.xx => 4.xx), tidak perlu khawatir tentang merusak perubahan.
Abu

45

Hanya posting sintaks Array untuk sama kolom OR query untuk bantuan mengintip keluar.

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

2
Pertanyaannya memeriksa John terhadap nama dan Smith terhadap nama belakang
steven_noble

14
@steven_noble - itulah mengapa saya mencetak tebal "kolom yang sama" dengan kueri atau. Saya dapat menghapus komentar tersebut sama sekali, tetapi saya pikir akan membantu jika orang membaca dengan teliti "atau" pertanyaan SO.
daino3

38

Pembaruan untuk Rails4

tidak membutuhkan permata pihak ke-3

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

Jawaban lama

tidak membutuhkan permata pihak ke-3

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

5
ini benar-benar satu-satunya jawaban murni untuk pertanyaan OP dalam hal sumber daya dan metode. jawaban lain baik (a) memerlukan apis pihak ketiga atau (b) memerlukan interpolasi oratau operator lain dalam blok teks, tidak ada yang tercermin dalam kode OP.
allenwlee

6
inilah artikel yang layak menjelaskannya coderwall.com/p/dgv7ag/…
allenwlee

4
Digunakan ...where_values.join(' OR ')dalam kasus saya
Sash

2
Anda dapat menghilangkan .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }bagian tersebut jika cakupan Anda tidak memiliki variabel yang perlu diikat.
fatuhoku

23

Jika ada yang mencari jawaban terbaru untuk pertanyaan ini, sepertinya sudah ada permintaan pull untuk memasukkan ini ke Rails: https://github.com/rails/rails/pull/9052 .

Berkat patch monyet @ j-mcnally untuk ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) Anda dapat melakukan hal berikut:

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

Yang lebih berharga adalah kemampuan untuk merangkai cakupan dengan 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) }

Kemudian Anda dapat menemukan semua Orang dengan nama depan atau belakang atau yang orang tuanya dengan nama belakang

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

Bukan contoh terbaik untuk penggunaan ini, tetapi hanya mencoba menyesuaikannya dengan pertanyaan.


2
Versi atau Rails mana yang Anda butuhkan untuk ini?
Selempang

3
Diskusi terbaru dan permintaan penarikan yang akan menjadikannya inti Rails dapat ditemukan di sini: github.com/rails/rails/pull/16052 Rails 5.0 ditargetkan untuk rilis resmi dari .orfungsionalitas rantai. Saya bisa menggunakan patch monyet yang saya sebutkan di Rails> = 3.2; namun Anda mungkin ingin menunggu Rails 5 untuk memperkenalkan perubahan lama pada kode Anda.
codenamev

1
Ya, itu digabungkan dan dirilis dengan Rails 5.
amoebe

22

Anda juga dapat menggunakan permata MetaWhere untuk tidak mencampurkan kode Anda dengan barang SQL:

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

3
+1. Pengganti MetaWhere adalah squeel oleh penulis yang sama.
Thilo

10

Bagi saya (Rails 4.2.5) hanya berfungsi seperti ini:

{ where("name = ? or name = ?", a, b) }

8

Ini akan menjadi kandidat yang baik untuk MetaWhere jika Anda menggunakan Rails 3.0+, tetapi tidak berfungsi di Rails 3.1. Anda mungkin ingin mencoba squeel . Itu dibuat oleh penulis yang sama. Inilah cara Anda melakukan rantai berbasis ATAU:

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

Anda dapat mencampur dan mencocokkan DAN / ATAU, di antara banyak hal keren lainnya .


8

Versi terbaru dari Rails / ActiveRecord mungkin mendukung sintaks ini secara native. Ini akan terlihat seperti:

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

Seperti disebutkan dalam permintaan tarik ini https://github.com/rails/rails/pull/9052

Untuk saat ini, cukup berpegang pada hal-hal berikut sudah bagus:

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

5
Tidak didukung di Rails 4.2.5
fatuhoku

1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')tidak bekerja. Ini harus Foo.where (foo: 'bar1'). Or.where (Foo.where (bar: 'bar2'))
Ana María Martínez Gómez

6

Rel 4 + Lingkup + 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

Saya mencoba merangkai lingkup bernama dengan .atau dan tidak berhasil, tetapi ini berhasil menemukan apa pun dengan set boolean itu. Menghasilkan seperti SQL

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

6

Jika Anda ingin memberikan ruang lingkup (alih-alih secara eksplisit mengerjakan seluruh kumpulan data) inilah yang harus Anda lakukan dengan Rails 5:

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

Atau:

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

Ini benar, tetapi jawaban yang lebih literal daripada hal yang sama yang secara teknis dilakukan stackoverflow.com/a/42894753/1032882 .
RudyOnRails

4

Rel 4

scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }

3

Jika Anda tidak dapat menulis klausa where secara manual untuk menyertakan pernyataan "atau" (yaitu, Anda ingin menggabungkan dua cakupan), Anda dapat menggunakan gabungan:

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

(sumber: ActiveRecord Query Union )

Ini akan mengembalikan semua rekaman yang cocok dengan kueri mana pun. Namun, ini mengembalikan array, bukan arel. Jika Anda benar-benar ingin mengembalikan arel, Anda memeriksa inti ini: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

Ini akan berhasil, selama Anda tidak keberatan dengan rel penambal monyet.


2

Lihat juga pertanyaan terkait ini: di sini , di sini , dan di sini

Untuk rel 4, berdasarkan artikel ini dan jawaban asli ini

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

Perhatikan peringatan artikel di akhir:

Rel 4.1+

Rails 4.1 memperlakukan default_scope sebagai cakupan biasa. Cakupan default (jika ada) disertakan dalam hasil where_values ​​dan inject (: atau) akan menambahkan atau menyatakan antara cakupan default dan wheres Anda. Itu buruk.

Untuk mengatasinya, Anda hanya perlu membatalkan kueri.


1

Ini adalah cara yang sangat nyaman dan berfungsi dengan baik di 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

yang squeelpermata menyediakan cara yang sangat mudah untuk mencapai hal ini (sebelum ini saya menggunakan sesuatu seperti metode @ coloradoblue ini):

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

0

Jadi, jawaban untuk pertanyaan awal, dapatkah Anda menggabungkan cakupan dengan 'atau' bukannya 'dan' tampaknya "tidak, Anda tidak bisa". Tetapi Anda dapat memberikan kode pada lingkup atau kueri yang sama sekali berbeda yang melakukan pekerjaan itu, atau menggunakan kerangka kerja yang berbeda dari ActiveRecord misalnya MetaWhere atau Squeel. Tidak berguna dalam kasus saya

I'm 'or'ing a scope generated by pg_search, yang melakukan lebih dari sekadar memilih, itu termasuk pesanan oleh ASC, yang membuat kekacauan dari persatuan yang bersih. Saya ingin 'atau' dengan cakupan buatan tangan yang melakukan hal-hal yang tidak dapat saya lakukan di pg_search. Jadi saya harus melakukannya seperti ini.

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

Yaitu mengubah cakupan menjadi sql, menempatkan tanda kurung di sekitar masing-masing, menggabungkannya bersama-sama dan kemudian find_by_sql menggunakan sql yang dihasilkan. Agak sampah, tapi berhasil.

Tidak, jangan beri tahu saya bahwa saya dapat menggunakan "against: [: name,: code]" di pg_search, saya ingin melakukannya seperti itu, tetapi kolom 'name' adalah hstore, yang tidak dapat ditangani pg_search namun. Jadi ruang lingkup berdasarkan nama harus dibuat dengan tangan dan kemudian digabungkan dengan ruang lingkup pg_search.


-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
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.