如何使用 OR 而不是 AND 链接范围查询?
Posted
技术标签:
【中文标题】如何使用 OR 而不是 AND 链接范围查询?【英文标题】:How to chain scope queries with OR instead of AND? 【发布时间】:2011-04-10 16:53:14 【问题描述】:我正在使用 Rails3,ActiveRecord
只是想知道如何使用 OR 语句而不是 AND 链接范围。
例如
Person.where(:name => "John").where(:lastname => "Smith")
正常返回:
name = 'John' AND lastname = 'Smith'
但我想:
`name = 'John' OR lastname = 'Smith'
【问题讨论】:
【参考方案1】:我现在在 Rails 6 中工作,现在看来这是可能的。使用来自 OP 的查询:
# in the Person model:
scope :john, -> where(name: "John")
scope :smith, -> where(lastname: "Smith")
scope :john_or_smith, -> john.or(self.smith)
【讨论】:
【参考方案2】:这是一种非常方便的方式,并且在 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")
)
【讨论】:
【参考方案3】:如果您希望提供一个范围(而不是显式处理整个数据集),那么您应该使用 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
【讨论】:
【参考方案4】:因此,原始问题的答案是,您是否可以使用“或”而不是“和”来加入范围,这似乎是“不,你不能”。但是您可以手动编写一个完全不同的范围或查询来完成这项工作,或者使用与 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 find_by_sql。这有点垃圾,但确实有效。
不,不要告诉我我可以在 pg_search 中使用 "against: [:name,:code]",我想这样做,但是 'name' 字段是一个 hstore,pg_search 可以还没处理因此,必须手工制作名称范围,然后将其与 pg_search 范围联合。
【讨论】:
【参考方案5】:根据this pull request,Rails 5 现在支持以下用于链接查询的语法:
Post.where(id: 1).or(Post.where(id: 2))
还有通过this gem.将该功能向后移植到Rails 4.2
【讨论】:
【参考方案6】:另请参阅这些相关问题:here、here 和 here
对于 rails 4,基于 this article 和 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
注意文章末尾的注意事项:
Rails 4.1+
Rails 4.1 将 default_scope 视为常规范围。默认 范围(如果有的话)包含在 where_values 结果中,并且 inject(:or) 将在默认范围和您的 哪里。这很糟糕。
要解决这个问题,您只需取消查询范围。
【讨论】:
【参考方案7】:对我来说(Rails 4.2.5)它只能这样工作:
where("name = ? or name = ?", a, b)
【讨论】:
【参考方案8】: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)
)
【讨论】:
【参考方案9】:只需发布 same 列 OR 查询的 Array 语法以帮助窥探。
Person.where(name: ["John", "Steve"])
【讨论】:
【参考方案10】:Rails/ActiveRecord 的更新版本可能原生支持这种语法。它看起来类似于:
Foo.where(foo: 'bar').or.where(bar: 'bar')
如此拉取请求中所述https://github.com/rails/rails/pull/9052
目前,只需坚持以下方法即可:
Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
【讨论】:
【参考方案11】: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
我尝试使用 .or 链接命名范围,但没有运气,但这适用于查找带有这些布尔值集的任何内容。生成类似 SQL 的
SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
【讨论】:
【参考方案12】:导轨 4
scope :combined_scope, -> where("name = ? or name = ?", 'a', 'b')
【讨论】:
【参考方案13】:如果你不能手动写出 where 子句来包含“or”语句(即你想合并两个作用域),你可以使用 union:
Model.find_by_sql("#Model.scope1.to_sql UNION #Model.scope2.to_sql")
(来源:ActiveRecord Query Union)
这将返回匹配任一查询的所有记录。但是,这会返回一个数组,而不是一个 arel。如果您真的想退货,请查看以下要点:https://gist.github.com/j-mcnally/250eaaceef234dd8971b。
只要您不介意猴子修补铁轨,就可以完成这项工作。
【讨论】:
【参考方案14】:squeel
gem 提供了一种非常简单的方法来实现这一点(在此之前我使用了类似@coloradoblue 的方法):
names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.wherename.like_any(names)
【讨论】:
【参考方案15】:如果有人正在寻找对此问题的更新答案,看起来有一个现有的拉取请求将其放入 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')
这不是使用这个的最佳例子,但只是试图将它与问题相匹配。
【讨论】:
【参考方案16】:你会的
Person.where('name=? OR lastname=?', 'John', 'Smith')
目前,新的 AR3 语法没有任何其他 OR 支持(即不使用某些 3rd 方 gem)。
【讨论】:
为了安全起见,值得将其表示为 Person.where("name = ? OR lastname = ?", 'John', 'Smith')【参考方案17】:names = ["tim", "tom", "bob", "alex"]
sql_string = names.map |t| "name = '#t'" .join(" OR ")
@people = People.where(sql_string)
【讨论】:
这允许通过#t
构造进行SQL 注入。【参考方案18】:
如果您使用的是 Rails 3.0+,这将是 MetaWhere 的理想选择,但它不适用于 Rails 3.1。您可能想改用squeel。它是同一作者制作的。以下是您如何执行基于 OR 的链:
Person.where(name == "John") | (lastname == "Smith")
您可以混合和匹配 AND/OR 以及许多其他 awesome things。
【讨论】:
【参考方案19】:您还可以使用MetaWhere gem 来避免将您的代码与 SQL 内容混淆:
Person.where((:name => "John") | (:lastname => "Smith"))
【讨论】:
【参考方案20】:使用ARel
t = Person.arel_table
results = Person.where(
t[:name].eq("John").
or(t[:lastname].eq("Smith"))
)
【讨论】:
以上是关于如何使用 OR 而不是 AND 链接范围查询?的主要内容,如果未能解决你的问题,请参考以下文章
使用重复参数而不是链接过滤器时,如何使用 Q 对象进行 AND 查找?