【问题标题】:How to chain scope queries with OR instead of AND?如何使用 OR 而不是 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'

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 activerecord rails-activerecord


    【解决方案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】:

              另请参阅这些相关问题:hereherehere

              对于 rails 4,基于 this articlethis 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.where{name.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"))
                                      )
                                      

                                      【讨论】:

                                        猜你喜欢
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2021-06-18
                                        • 2010-12-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2010-09-26
                                        相关资源
                                        最近更新 更多