IT

AND 대신 OR로 범위 쿼리를 연결하는 방법은 무엇입니까?

lottoking 2020. 7. 2. 07:26
반응형

AND 대신 OR로 범위 쿼리를 연결하는 방법은 무엇입니까?


Rails3, ActiveRecord를 사용하고 있습니다

AND가 아닌 OR 문으로 범위를 연결하는 방법이 궁금합니다.

예 :

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

일반적으로 다음을 반환합니다.

name = 'John' AND lastname = 'Smith'

그러나 나는 싶습니다 :

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

당신은 할 것

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

현재 새로운 AR3 구문에 의한 다른 OR 지원은 없습니다 (즉, 일부 타사 gem을 사용하지 않음).


ARel 사용

t = Person.arel_table

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

이 풀 요청 에 따르면 Rails 5는 이제 쿼리 연결을 위해 다음 구문을 지원합니다.

Post.where(id: 1).or(Post.where(id: 2))

이 gem을 통해 Rails 4.2에 기능의 백 포트도 있습니다 .


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

동일한 열 또는 쿼리에 대해 배열 구문을 게시하면 엿볼 수 있습니다.

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

MetaWhere gem을 사용 하여 코드를 SQL과 섞지 않을 수도 있습니다 .

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

누구 든지이 답변에 대한 업데이트 된 답변을 찾고 있다면 Rails로 가져 오는 기존 풀 요청이있는 것 같습니다 : https://github.com/rails/rails/pull/9052 .

ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b )에 대한 @ j-mcnally의 원숭이 패치 덕분에 다음을 수행 할 수 있습니다.

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

이것을 사용하는 가장 좋은 예는 아니지만 질문에 맞추려고합니다.


나를 위해 (Rails 4.2.5) 다음과 같이 작동합니다.

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

Rails 3.0 이상을 사용하는 경우 MetaWhere에 적합하지만 Rails 3.1에서는 작동하지 않습니다. 대신에 압박 을 시도해 볼 수 있습니다 . 같은 저자에 의해 만들어졌습니다. OR 기반 체인을 수행하는 방법은 다음과 같습니다.

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

당신은 다른 많은 멋진 것들 중에서 AND / OR를 혼합하고 일치시킬 수 있습니다 .


업데이트 된 Rails / ActiveRecord 버전은이 구문을 기본적으로 지원할 수 있습니다. 다음과 유사합니다.

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

이 풀 요청에 명시된 바와 같이 https://github.com/rails/rails/pull/9052

지금은 단순히 다음을 고수하는 것이 좋습니다.

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

레일 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

나는 .or와 운이없는 스코프라는 체인을 시도했지만 이것은 부울 세트가있는 것을 찾는 데 효과적이었습니다. 같은 SQL을 생성

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

레일 4

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

If you can't write out the where clause manually to include the "or" statement (ie, you want to combine two scopes), you can use union:

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

(source: ActiveRecord Query Union)

This is will return all records matching either query. However, this returns an array, not an arel. If you really want to return an arel, you checkout this gist: https://gist.github.com/j-mcnally/250eaaceef234dd8971b.

This will do the job, as long as you don't mind monkey patching rails.


If you're looking to provide a scope (instead of explicitly working on the whole dataset) here's what you should do with Rails 5:

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

Or:

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

Also see these related questions: here, here and here

For rails 4, based on this article and this original answer

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

Note the article's caution at the end:

Rails 4.1+

Rails 4.1 treats default_scope just as a regular scope. The default scope (if you have any) is included in the where_values result and inject(:or) will add or statement between the default scope and your wheres. That's bad.

To solve that, you just need to unscope the query.


the squeel gem provides an incredibly easy way to accomplish this (prior to this I used something like @coloradoblue's method):

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

So the answer to the original question, can you join scopes with 'or' instead of 'and' seems to be "no you can't". But you can hand code a completely different scope or query that does the job, or use a different framework from ActiveRecord e.g. MetaWhere or Squeel. Not useful in my case

I'm 'or'ing a scope generated by pg_search, which does a bit more than select, it includes order by ASC, which makes a mess of a clean union. I want to 'or' it with a handcrafted scope that does stuff I can't do in pg_search. So I've had to do it like this.

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

I.e. turn the scopes into sql, put brackets around each one, union them together and then find_by_sql using the sql generated. It's a bit rubbish, but it does work.

No, don't tell me I can use "against: [:name,:code]" in pg_search, I'd like to do it like that, but the 'name' field is an hstore, which pg_search can't handle yet. So the scope by name has to be hand crafted and then unioned with the pg_search scope.


This is a very convenient way and it works fine in 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")
  )

names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)

참고URL : https://stackoverflow.com/questions/3684311/how-to-chain-scope-queries-with-or-instead-of-and

반응형