sql-ActiveRecord Arel或条件

如何使用逻辑OR而非AND组合2种不同的条件?

注意:2个条件是作为Rails示波器生成的,不能轻易将其直接更改为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自述文件):

尚不支持OR运算符

但我希望它在这里不适用,并希望编写如下内容:

(admins.or authors).to_sql
10个解决方案
93 votes

ActiveRecord查询是query.all对象(疯狂地不支持User < ActiveRecord::Base),而不是Arel对象(支持)。

[更新:从Rails 5开始,query.all中支持“或”; 参见[https://stackoverflow.com/a/33248299/190135]]

但幸运的是,他们的query.all方法接受ARel查询对象。 所以如果User < ActiveRecord::Base ...

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

query.all现在可以放心:

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执行实际的数据库调用。

AlexChaffee answered 2019-11-05T11:20:49Z
70 votes

我参加聚会有点晚了,但这是我能提出的最好建议:

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'))"
jswanner answered 2019-11-05T11:19:48Z
9 votes

在实际的竞技场页面上:

OR运算符的工作方式如下:

users.where(users[:name].eq('bob').or(users[:age].lt(25)))
Dave Newton answered 2019-11-05T11:21:32Z
8 votes

从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')
pje answered 2019-11-05T11:22:05Z
3 votes

我遇到了同样的问题,正在寻找替代mongoid #any_of的activerecord。

@jswanner的答案是好的,但是仅在where参数是哈希的情况下才有效:

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

编辑:从那时起,我发布了一个gem来帮助构建OR查询。

kik answered 2019-11-05T11:23:09Z
1 votes

只需为您的OR条件设定范围:

scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
Unixmonkey answered 2019-11-05T11:23:36Z
0 votes

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

您可能会认为我有偏见,但在这种特殊情况下,我仍然认为传统的数据结构操作比方法链接要清晰得多且方便得多。

Alex Fortuna answered 2019-11-05T11:24:17Z
0 votes

扩展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))
equivalent8 answered 2019-11-05T11:24:49Z
-2 votes

关于这种方法呢:[http://guides.rubyonrails.org/active_record_querying.html#hash-conditions](并检查2.3.3)

admins_or_authors = User.where(:kind => [:admin, :author])
Sjors Branderhorst answered 2019-11-05T11:25:17Z
-4 votes

不幸的是,它本身不受支持,因此我们需要在这里进行破解。

hack看起来像这样,这是相当低效的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角度来看,更好的hack看起来像这样:

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。

Dmytrii Nagirniak answered 2019-11-05T11:26:31Z
translate from https://stackoverflow.com:/questions/7976358/activerecord-arel-or-condition