Làm cách nào để xâu chuỗi các truy vấn phạm vi bằng OR thay vì AND?


137

Tôi đang sử dụng Rails3, ActiveRecord

Chỉ cần tự hỏi làm thế nào tôi có thể xâu chuỗi phạm vi với các câu lệnh OR chứ không phải AND.

ví dụ

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

Điều đó thường trả về:

name = 'John' AND lastname = 'Smith'

nhưng tôi muốn:

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

Câu trả lời:


107

Bạn sẽ làm

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

Ngay bây giờ, không có bất kỳ sự hỗ trợ HOẶC nào khác theo cú pháp AR3 mới (nghĩa là không sử dụng một số đá quý của bên thứ 3).


21
và chỉ vì mục đích an toàn, đáng để thể hiện điều này dưới dạng Person.where ("name =? OR Lastname =?", 'John', 'Smith')
CambridgeMike

6
Điều này vẫn được áp dụng trong Rails 4? Không có gì tốt hơn đến trong Rails 4?
Donato

11
Không có thay đổi trong Rails 4, nhưng Rails 5 sẽ .orđược tích hợp sẵn .
vôi

3
đây không phải là phạm vi, chỉ là 2 trong đó các mệnh đề hoặc trường hợp phạm vi rất cụ thể. Điều gì sẽ xảy ra nếu tôi muốn xâu chuỗi các phạm vi phức tạp hơn, ví dụ như tham gia.
Miguelfg

2
Với Rails 5, bạn cũng có thể làm một cái gì đó như, Person.where ("name = 'John'"). Hoặc (Person.where ("lastname = 'Smith'"))
shyam


48

Sử dụng ARel

t = Person.arel_table

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

1
Bất cứ ai cũng có thể nhận xét về loại truy vấn này phá vỡ trong Postgres?
Olivier Lacan

ARel được coi là thuyết bất khả tri.
Simon Perepelitsa

Mặc dù điều này có vẻ là một ý tưởng hay, ARel không phải là API công khai và việc sử dụng này có thể phá vỡ ứng dụng của bạn theo những cách không mong muốn, vì vậy tôi khuyên bạn nên chống lại nó trừ khi bạn chú ý đến sự phát triển của API ARel.
Olivier Lacan

7
@OlivierLacan Arel là một API công khai, hay chính xác hơn là nó hiển thị một API. Active Record chỉ cung cấp các phương thức tiện lợi sử dụng nó dưới mui xe, Hoàn toàn hợp lệ để sử dụng nó theo cách riêng của mình. Nó tuân theo phiên bản ngữ nghĩa, vì vậy trừ khi bạn thay đổi các phiên bản chính (3.xx => 4.xx), không cần phải lo lắng về việc phá vỡ các thay đổi.
Xám

41

Chỉ cần đăng cú pháp Array cho cùng một cột HOẶC truy vấn để giúp nhìn ra.

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

2
Vui lòng cung câu hỏi John chống lại tên và Smith chống lastname
steven_noble

11
@steven_noble - đó là lý do tại sao tôi in đậm "cùng cột" với hoặc truy vấn. Tôi có thể xóa bình luận hoàn toàn, nhưng nghĩ rằng nó sẽ hữu ích cho những người đọc câu hỏi "hoặc" truy vấn SO.
daino3

38

Cập nhật cho Rails4

không yêu cầu đá quý bên thứ 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) }

Câu trả lời cũ

không yêu cầu đá quý bên thứ 3

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

5
đây thực sự là câu trả lời thuần túy duy nhất cho câu hỏi của OP về tài nguyên và phương pháp. các câu trả lời khác (a) yêu cầu apis của bên thứ 3 hoặc (b) yêu cầu nội suy orhoặc các toán tử khác trong một khối văn bản, cả hai đều không được phản ánh trong mã của OP.
allenwlee


4
Được sử dụng ...where_values.join(' OR ')trong trường hợp của tôi
Sash

2
Bạn có thể bỏ qua .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }phần nếu phạm vi của bạn không có bất kỳ biến nào cần ràng buộc.
fatuhoku

22

Bạn cũng có thể sử dụng đá quý MetaWhere để không trộn lẫn mã của mình với các công cụ SQL:

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

3
+1. Sự kế thừa cho MetaWhere được thực hiện bởi cùng một tác giả.
Thilo

22

Trong trường hợp bất cứ ai đang tìm kiếm một câu trả lời cập nhật cho câu trả lời này, có vẻ như có một yêu cầu kéo hiện có để đưa câu hỏi này vào Rails: https://github.com/rails/rails/pull/9052 .

Nhờ bản vá khỉ của @ j-mcnally cho ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ), bạn có thể làm như sau:

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

Thậm chí giá trị hơn là khả năng chuỗi phạm vi với 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) }

Sau đó, bạn có thể tìm thấy tất cả những người có họ hoặc tên hoặc cha mẹ có họ

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

Không phải là ví dụ tốt nhất cho việc sử dụng này, nhưng chỉ cố gắng để phù hợp với nó với câu hỏi.


2
Phiên bản hoặc Rails nào bạn cần cho việc này?
Sash

3
Thảo luận mới nhất và yêu cầu kéo sẽ biến điều này thành lõi Rails có thể được tìm thấy ở đây: github.com/rails/rails/pull/16052 Rails 5.0 được nhắm mục tiêu phát hành chính thức .orchức năng chuỗi. Tôi đã có thể sử dụng bản vá khỉ mà tôi đã đề cập trên Rails> = 3.2; tuy nhiên, bạn có thể đợi Rails 5 giới thiệu bất kỳ thay đổi lâu dài nào đối với mã của bạn.
codenamev

1
Đúng, nó đã được hợp nhất và phát hành với Rails 5.
amoebe

10

Đối với tôi (Rails 4.2.5) nó chỉ hoạt động như thế này:

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

8

Đây sẽ là một ứng cử viên sáng giá cho MetaWhere nếu bạn đang sử dụng Rails 3.0+, nhưng nó không hoạt động trên Rails 3.1. Bạn có thể muốn thử squeel thay thế. Nó được thực hiện bởi cùng một tác giả. Đây là cách bạn sẽ thực hiện chuỗi dựa trên HOẶC:

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

Bạn có thể trộn và kết hợp VÀ / HOẶC, trong số nhiều thứ tuyệt vời khác .


8

Phiên bản cập nhật của Rails / ActiveRecord có thể hỗ trợ cú pháp này nguyên bản. Nó sẽ trông giống như:

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

Như đã lưu ý trong yêu cầu kéo này https://github.com/rails/rails/pull/9052

Bây giờ, chỉ cần gắn bó với các công việc sau đây là tuyệt vời:

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

5
Không được hỗ trợ trong Rails 4.2.5
fatuhoku 29/07/2016

1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')không hoạt động. Nó phải là Foo.where (foo: 'bar1'). Hoặc.where (Foo.where (bar: 'bar2'))
Ana María Martínez Gómez

6

Đường ray 4 + Phạm vi + 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

Tôi đã thử xâu chuỗi các phạm vi có tên .or và không có may mắn, nhưng điều này đã làm việc để tìm bất cứ điều gì với các booleans được thiết lập. Tạo SQL như

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

4

Đường ray 4

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

4

Nếu bạn đang tìm cách cung cấp một phạm vi (thay vì làm việc rõ ràng trên toàn bộ tập dữ liệu) thì đây là những gì bạn nên làm với Rails 5:

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

Hoặc là:

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

Điều này là chính xác, nhưng một câu trả lời theo nghĩa đen nhiều hơn so với điều tương tự mà stackoverflow.com/a/42894753/1032882 về mặt kỹ thuật.
RudyOnRails

3

Nếu bạn không thể viết ra mệnh đề where theo cách thủ công để bao gồm câu lệnh "hoặc" (nghĩa là bạn muốn kết hợp hai phạm vi), bạn có thể sử dụng union:

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

(nguồn: Liên minh truy vấn ActiveRecord )

Điều này sẽ trả về tất cả các hồ sơ phù hợp với một trong hai truy vấn. Tuy nhiên, điều này trả về một mảng, không phải là một arel. Nếu bạn thực sự muốn trả lại một arel, bạn hãy kiểm tra ý chính này: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

Điều này sẽ thực hiện công việc, miễn là bạn không bận tâm đến việc vá khỉ.


2

Cũng xem những câu hỏi liên quan: ở đây , ở đâyở đây

Đối với đường ray 4, dựa trên bài viết nàycâu trả lời ban đầu này

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

Lưu ý thận trọng của bài viết ở cuối:

Đường ray 4.1+

Rails 4.1 coi default_scope giống như một phạm vi thông thường. Phạm vi mặc định (nếu bạn có bất kỳ) được bao gồm trong kết quả where_values ​​và tiêm (: hoặc) sẽ thêm hoặc tuyên bố giữa phạm vi mặc định và vị trí của bạn. Điều đó thật xấu.

Để giải quyết điều đó, bạn chỉ cần bỏ theo dõi truy vấn.


1

Đây là một cách rất thuận tiện và nó hoạt động tốt trong 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

các squeelđá quý cung cấp một cách vô cùng dễ dàng để thực hiện điều này (trước khi tôi đã sử dụng một cái gì đó này như phương pháp @ coloradoblue của):

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

0

Vì vậy, câu trả lời cho câu hỏi ban đầu, bạn có thể tham gia phạm vi với 'hoặc' thay vì 'và' dường như là "không bạn không thể". Nhưng bạn có thể mã hóa một phạm vi hoặc truy vấn hoàn toàn khác thực hiện công việc hoặc sử dụng một khung khác với ActiveRecord, ví dụ như MetaWhere hoặc Squeel. Không hữu ích trong trường hợp của tôi

Tôi đang hoặc là một phạm vi được tạo bởi pg_search, hoạt động nhiều hơn một chút so với lựa chọn, nó bao gồm thứ tự của ASC, điều này tạo ra sự lộn xộn của một liên minh sạch. Tôi muốn 'hoặc' nó với một phạm vi thủ công làm những thứ tôi không thể làm trong pg_search. Vì vậy, tôi đã phải làm điều đó như thế này.

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

Tức là biến phạm vi thành sql, đặt dấu ngoặc quanh mỗi cái, liên kết chúng lại với nhau và sau đó find_by_sql bằng cách sử dụng sql được tạo. Đó là một chút rác, nhưng nó hoạt động.

Không, đừng nói với tôi rằng tôi có thể sử dụng "chống lại: [: name ,: code]" trong pg_search, tôi muốn làm như vậy, nhưng trường 'name' là một cửa hàng, mà pg_search không thể xử lý chưa. Vì vậy, phạm vi theo tên phải được tạo thủ công và sau đó kết hợp với phạm vi pg_search.


-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.