it-roy-ru.com

Как связать запросы области с помощью OR вместо AND?

Я использую Rails3, ActiveRecord

Просто интересно, как я могу связать области видимости с помощью выражений OR вместо AND.

например.

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

Это обычно возвращает:

name = 'John' AND lastname = 'Smith'

но я бы хотел:

`name = 'John' OR lastname = 'Smith'
128
user410715

Вы бы сделали

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

Прямо сейчас нет никакой другой поддержки OR новым синтаксисом AR3 (то есть без использования какого-либо стороннего гема).

94
Petros

Используйте ARel

t = Person.arel_table

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

Согласно этот запрос на извлечение , Rails 5 теперь поддерживает следующий синтаксис для цепочки запросов:

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

Есть также бэкпорт функциональности в Rails 4.2 через этот драгоценный камень.

39
Ryan Leaf

Обновление для 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)
)
37
kissrobber

Просто выложите синтаксис Array для same column OR запросов, чтобы выглянуло.

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

В случае, если кто-то ищет обновленный ответ на этот вопрос, похоже, что существует существующий пул-запрос для передачи его в 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')

Не лучший пример для использования этого, но просто пытаюсь соответствовать этому вопросу.

22
codenamev

Вы также можете использовать MetaWhere gem, чтобы не перепутать ваш код с SQL:

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

Обновленная версия Rails/ActiveRecord может изначально поддерживать этот синтаксис. Это будет выглядеть примерно так:

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

Как отмечается в этом запросе на извлечение https://github.com/Rails/rails/pull/9052

На данный момент просто придерживаться следующих работ прекрасно работает:

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

Это было бы хорошим кандидатом для MetaWhere, если вы используете Rails 3.0+, но это не работает на Rails 3.1. Вы можете попробовать squeel вместо этого. Это сделано тем же автором. Вот как бы вы выполнили цепочку на основе OR:

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

Вы можете смешивать и сочетать И/ИЛИ, среди многих других удивительные вещи .

8
dhulihan

Для меня (Rails 4.2.5) это работает только так:

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

Рельсы 4 + Сфера + Арель

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

Рельсы 4

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

Если вы не можете выписать предложение where вручную, чтобы включить оператор "или" (т. Е. Хотите объединить две области), вы можете использовать union:

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

(источник: ActiveRecord Query Union )

Это вернет все записи, соответствующие любому запросу. Тем не менее, это возвращает массив, а не arel. Если вы действительно хотите вернуть верблюда, вы извлекаете из этого Gist: https://Gist.github.com/j-mcnally/250eaaceef234dd8971b

Это сработает, если вы не возражаете против того, чтобы патчить Rails. 

3
Jake Christensen

Также см. Эти связанные вопросы: здесь , здесь и здесь

Для Rails 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

Примечание предостережение статьи в конце:

Rails 4.1+

Rails 4.1 рассматривает default_scope как обычную область видимости. По умолчанию область действия (если есть) включена в результат where_values ​​и Inject (: или) добавит или оператор между областью по умолчанию и вашим Wheres. Это плохо.

Чтобы решить эту проблему, вам просто нужно отменить выбор запроса.

2
HeyZiko

Это очень удобный способ, и он отлично работает в 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")
  )
0
Jigar Bhatt

гем squeel обеспечивает невероятно простой способ сделать это (до этого я использовал что-то вроде метода @ coloradoblue):

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

Если вы хотите предоставить область (вместо явной работы со всем набором данных), вот что вы должны сделать с 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
0
thisismydesign

Таким образом, ответ на первоначальный вопрос, можете ли вы присоединиться к областям действия с помощью «или» вместо «и», кажется, «нет, вы не можете». Но вы можете передать код совершенно другой области или запрос, который выполняет эту работу, или использовать другую среду, отличную от ActiveRecord, например. MetaWhere или Squeel. Не полезно в моем случае

Я 'или' область видимости, сгенерированная pg_search, которая делает немного больше, чем select, она включает в себя порядок ASC, что делает беспорядок чистого объединения. Я хочу «или» это с ручной областью, которая делает вещи, которые я не могу сделать в pg_search. Так что я должен был сделать это так.

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

То есть превратить области видимости в sql, заключить их в квадратные скобки, объединить их вместе и затем find_by_sql, используя сгенерированный sql. Это немного мусора, но это работает.

Нет, не говорите мне, что я могу использовать "против: [: name,: code]" в pg_search, я хотел бы сделать это так, но поле 'name' - это hstore, которое pg_search не может обработать еще. Таким образом, область действия по имени должна быть создана вручную, а затем объединена с областью действия pg_search. 

0
John Small