your programing

ActiveRecord Arel OR 조건

lovepro 2020. 12. 26. 16:15
반응형

ActiveRecord Arel OR 조건


AND 대신 논리 OR을 사용하여 두 가지 조건을 결합하는 방법은 무엇입니까?

참고 : 2 개의 조건이 레일 범위로 생성되며 where("x or y")직접 같은 것으로 쉽게 변경할 수 없습니다 .

간단한 예 :

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

AND 조건을 적용하는 것은 쉽습니다 (이 특정 경우에는 의미가 없음).

(admins.merge authors).to_sql
#=> select ... from ... where kind = 'admin' AND kind = 'author'

그러나 이미 사용 가능한 두 가지 Arel 관계가있는 다음 쿼리를 어떻게 생성 할 수 있습니까?

#=> select ... from ... where kind = 'admin' OR kind = 'author'

Arel readme에 따르면 다음과 같습니다 .

OR 연산자는 아직 지원되지 않습니다.

그러나 여기에 적용되지 않기를 바라며 다음과 같이 쓸 것으로 기대합니다.

(admins.or authors).to_sql

나는 파티에 조금 늦었지만 여기에 내가 생각 해낼 수있는 가장 좋은 제안이있다.

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

admins = admins.where_values.reduce(:and)
authors = authors.where_values.reduce(:and)

User.where(admins.or(authors)).to_sql
# => "SELECT \"users\".* FROM \"users\"  WHERE ((\"users\".\"kind\" = 'admin' OR \"users\".\"kind\" = 'author'))"

ActiveRecord 쿼리는 Arel 개체 ( ActiveRecord::Relation지원하지 않음 or)가 아니라 개체 (매우 지원하지 않음 )입니다.

[ 업데이트 : Rails 5부터 "or"는 ActiveRecord::Relation; 참조 https://stackoverflow.com/a/33248299/190135를 ]

그러나 운 좋게도 그들의 where방법은 ARel 쿼리 객체를 허용합니다. 그래서 만약 User < ActiveRecord::Base...

users = User.arel_table
query = User.where(users[:kind].eq('admin').or(users[:kind].eq('author')))

query.to_sql 이제 안심할 수 있습니다.

SELECT "users".* FROM "users"  WHERE (("users"."kind" = 'admin' OR "users"."kind" = 'author'))

명확성을 위해 일부 임시 부분 쿼리 변수를 추출 할 수 있습니다.

users = User.arel_table
admin = users[:kind].eq('admin')
author = users[:kind].eq('author')
query = User.where(admin.or(author))

그리고 당연히 쿼리가 있으면 query.all실제 데이터베이스 호출을 실행하는 데 사용할 수 있습니다 .


로부터 실제 arel 페이지 :

OR 연산자는 다음과 같이 작동합니다.

users.where(users[:name].eq('bob').or(users[:age].lt(25)))

Rails 5부터는 ActiveRecord::Relation#or다음을 수행 할 수 있습니다.

User.where(kind: :author).or(User.where(kind: :admin))

... 예상했던 SQL로 변환됩니다.

>> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql
SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin')

mongoid에 대한 activerecord 대안을 찾고 같은 문제에 부딪 혔습니다 #any_of.

@jswanner 대답은 좋지만 where 매개 변수가 Hash 인 경우에만 작동합니다.

> User.where( email: 'foo', first_name: 'bar' ).where_values.reduce( :and ).method( :or )                                                
=> #<Method: Arel::Nodes::And(Arel::Nodes::Node)#or>

> User.where( "email = 'foo' and first_name = 'bar'" ).where_values.reduce( :and ).method( :or )                                         
NameError: undefined method `or' for class `String'

문자열과 해시를 모두 사용하려면 다음을 사용할 수 있습니다.

q1 = User.where( "email = 'foo'" )
q2 = User.where( email: 'bar' )
User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) )

실제로 그것은 추악하며 매일 사용하고 싶지 않습니다. #any_of내가 만든 몇 가지 구현 은 다음과 같습니다 . https://gist.github.com/oelmekki/5396826

그것을하자 :

> q1 = User.where( email: 'foo1' ); true                                                                                                 
=> true

> q2 = User.where( "email = 'bar1'" ); true                                                                                              
=> true

> User.any_of( q1, q2, { email: 'foo2' }, "email = 'bar2'" )
User Load (1.2ms)  SELECT "users".* FROM "users" WHERE (((("users"."email" = 'foo1' OR (email = 'bar1')) OR "users"."email" = 'foo2') OR (email = 'bar2')))

편집 : 그 이후로 OR 쿼리를 작성하는 데 도움이되는 gem을 게시 했습니다 .


OR 조건에 대한 범위를 만드십시오.

scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])

SmartTuple사용하면 다음과 같이 보일 것입니다.

tup = SmartTuple.new(" OR ")
tup << {:kind => "admin"}
tup << {:kind => "author"}
User.where(tup.compile)

또는

User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile)

내가 편견이라고 생각할 수도 있지만, 이 특별한 경우에는 전통적인 데이터 구조 작업이 메서드 체인보다 훨씬 더 명확 하고 편리하다고 생각합니다.


To extend jswanner answer (which is actually awesome solution and helped me) for googling people:

you can apply scope like this

scope :with_owner_ids_or_global, lambda{ |owner_class, *ids|
  with_ids = where(owner_id: ids.flatten).where_values.reduce(:and)
  with_glob = where(owner_id: nil).where_values.reduce(:and)
  where(owner_type: owner_class.model_name).where(with_ids.or( with_glob ))
}

User.with_owner_ids_or_global(Developer, 1, 2)
# =>  ...WHERE `users`.`owner_type` = 'Developer' AND ((`users`.`owner_id` IN (1, 2) OR `users`.`owner_id` IS NULL))

What about this approach: http://guides.rubyonrails.org/active_record_querying.html#hash-conditions (and check 2.3.3)

admins_or_authors = User.where(:kind => [:admin, :author])

Unfortunately it is not supported natively, so we need to hack here.

And the hack looks like this, which is pretty inefficient SQL (hope DBAs are not looking at it :-) ):

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

both = User.where("users.id in (#{admins.select(:id)}) OR users.id in (#{authors.select(:id)})")
both.to_sql # => where users.id in (select id from...) OR users.id in (select id from)

This generates subselets.

And a little better hack (from SQL perspective) looks like this:

admins_sql = admins.arel.where_sql.sub(/^WHERE/i,'')
authors_sql = authors.arel.where_sql.sub(/^WHERE/i,'')
both = User.where("(#{admins_sql}) OR (#{authors_sql})")
both.to_sql # => where <admins where conditions> OR <authors where conditions>

This generates proper OR condition, but obviously it only takes into account the WHERE part of the scopes.

I chose the 1st one until I'll see how it performs.

In any case, you must be pretty careful with it and watch the SQL generated.

ReferenceURL : https://stackoverflow.com/questions/7976358/activerecord-arel-or-condition

반응형