簡體   English   中英

ActiveRecord Arel OR條件

[英]ActiveRecord Arel OR condition

如何使用邏輯OR而不是AND組合2個不同的條件?

注意:作為軌道范圍生成2個條件,並且不能直接將其更改為where("x or y")

簡單的例子:

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

它很容易應用和條件(對於這個特殊情況是沒有意義的):

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

但是,如何生成具有2種不同Arel關系的以下查詢?

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

似乎( 根據Arel自述文件 ):

OR運算符尚不支持

但是我希望它不適用於此並期望寫出如下內容:

(admins.or authors).to_sql

ActiveRecord查詢是ActiveRecord::Relation對象(令人生氣地不支持or ),而不是Arel對象(確實如此)。

[ 更新 :從Rails 5開始, 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來執行實際的數據庫調用。

我參加聚會有點晚了,但這是我能提出的最佳建議:

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

實際的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的#any_of的activerecord替代品。

@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#any_of

它讓我們這樣做:

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

編輯:從那時起,我發布了一個gem來幫助構建OR查詢

只需為你的OR條件做一個范圍:

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

為google搜索人員擴展jswanner答案 (這實際上是很棒的解決方案並幫助我):

你可以申請這樣的范圍

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

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

您可能認為我有偏見,但我仍然認為傳統的數據結構操作比這種特殊情況下的方法鏈更加清晰和方便。

這種方法怎么樣: http//guides.rubyonrails.org/active_record_querying.html#hash-conditions (並查看2.3.3)

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

不幸的是它本身不受支持,所以我們需要在這里破解。

黑客看起來像這樣,這是非常低效的SQL(希望DBA不看它:-)):

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)

這會生成子子列。

而一個更好的黑客(從SQL角度來看)看起來像這樣:

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>

這會生成正確的OR條件,但顯然它只考慮了范圍的WHERE部分。

我選擇了第一個,直到我看到它的表現。

無論如何,你必須非常小心它並觀察生成的SQL。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM