如何使用 OR 而不是 AND 来链接范围查询?

我正在使用 Rails3,ActiveRecord

只是想知道如何用 OR 语句而不是 AND 来链接作用域。

例如:。

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

这通常会返回:

name = 'John' AND lastname = 'Smith'

但我想:

`name = 'John' OR lastname = 'Smith'
144931 次浏览

你会的

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

现在,新的 AR3语法没有任何其他的 OR 支持(即不使用第三方 gem)。

使用 阿瑞尔

t = Person.arel_table


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

您还可以使用 MetaWhere gem 来避免将代码与 SQL 内容混淆:

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

如果你正在使用 Rails 3.0 + ,这将是 MetaWhere 的一个很好的候选者,但是它不能在 Rails 3.1上工作。你可能想试试 尖叫。作者是同一个人。下面是如何执行基于 OR 的链式操作:

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

您可以混合和匹配 AND/OR,以及许多其他 很棒的东西

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

如果有人正在寻找这个问题的更新答案,那么似乎有一个现有的 pull 请求将其放入 Rails: https://github.com/rails/rails/pull/9052

感谢@j-mcnally 的 ActiveRecord (https://gist.github.com/j-mcnally/250eaaceef234dd8971b)猴子补丁,您可以执行以下操作:

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

这不是使用它的最佳例子,只是试图将它与问题相匹配。

squeel gem 提供了一种难以置信的简单方法来实现这一点(在此之前,我使用了类似@color adoblue 的方法) :

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

如果您不能手动写出 where 子句来包含“ or”语句(即,您想合并两个作用域) ,您可以使用 union:

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

(资料来源: ActiveRecord 查询联盟)

这将返回与任一查询匹配的所有记录。但是,这将返回一个数组,而不是一个 arel。如果您真的想返回一个 arel,您可以查看以下要点: https://gist.github.com/j-mcnally/250eaaceef234dd8971b

只要你不介意修理铁轨,这个就可以了。

铁路4

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

Rails 4 + Scope + 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

我试过用。或者运气不好,但是在布尔型集合中找到任何东西都成功。生成如下 SQL

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

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

Rails/ActiveRecord 的更新版本可能本地支持这种语法:

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

正如在这个请求 https://github.com/rails/rails/pull/9052中指出的那样

就目前而言,只要坚持以下原则就行了:

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

只是张贴数组语法的 一样列或查询,以帮助窥视出来。

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

对我来说(Rails 4.2.5)它只能这样工作:

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

也可以看到这些相关的问题: 给你给你给你

对于轨道4,基于 这篇文章这个原始的答案

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

注意 文章末尾的警告:

Rails4.1 +

Rails 4.1将 default _ scope 视为常规范围 Scope (如果有的话)包含在 where _ value 结果和 将在默认作用域和您的 哪里,真糟糕。

要解决这个问题,您只需取消查询的范围。

根据 这个请求,Rails 5现在支持以下语法来进行链接查询:

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

还有一个通过 这颗宝石。将该功能导入 Rails 4.2的后端

因此,对于最初的问题,您能否用“ or”而不是“ and”来连接作用域,答案似乎是“不,您不能”。但是你可以手工编写完全不同的作用域或查询,或者使用不同于 ActiveRecord 的框架,例如 MetaWhere 或 Squeel。对我来说没什么用

我正在使用 pg _ search 生成的作用域,它的作用不仅仅是选择,它还包括 ASC 的订单,这使得一个干净的联合变得一团糟。我想用一个手工制作的作用域来“或者”它,这个作用域可以完成我在 pg _ search 中无法完成的任务。所以我不得不这么做。

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

例如,将范围转换为 sql,在每个范围前后放置方括号,将它们联合在一起,然后使用生成的 sql 查找 _ by _ sql。虽然有点垃圾,但还是有用的。

不,不要告诉我可以在 pg _ search 中使用“ against: [ : name,: code ]”,我想这样做,但是“ name”字段是 hstore,pg _ search 还不能处理。因此,必须手工制作名称作用域,然后将其与 pg _ search 作用域联合起来。

如果你想提供一个作用域(而不是显式地处理整个数据集) ,以下是你应该如何处理 Rails 5:

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

或者:

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

这是一种非常方便的方法,并且在 Rails5中运行良好:

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

我现在在 Rails 6工作,看起来现在这是可能的:

# in the Person model:
scope :john, -> { where(name: "John") }
scope :smith, -> { where(lastname: "Smith") }
scope :john_or_smith, -> { john.or(self.smith) }