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

137

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

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

например

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

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

name = 'John' AND lastname = 'Smith'

но я бы хотел:

`name = 'John' OR lastname = 'Smith'
user410715
источник
Может быть
полезным

Ответы:

107

Вы бы сделали

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

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

Петрос
источник
21
и просто ради безопасности, стоит выразить это как Person.where («имя =? ИЛИ фамилия =?», «Джон», «Смит»)
CambridgeMike
6
Это все еще применимо в Rails 4? Ничего лучшего не появилось в Rails 4?
Донато
11
Без изменений в Rails 4, но Rails 5 будет .orвстроен .
лайм
3
это не области видимости, это всего лишь 2 условия или очень конкретный случай. Что делать, если я хотел бы связать более сложные области, например, с соединениями.
miguelfg
2
С Rails 5 вы также можете сделать что-то вроде Person.where ("name = 'John'"). Или (Person.where ("lastname = 'Smith'"))
shyam
48

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

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)
Дэн МакНевин
источник
1
Кто-нибудь может прокомментировать, ломается ли этот тип запросов в Postgres?
Оливье Лакан
ARel должен быть DB-независимым.
Симон Перепелица
Хотя это выглядит хорошей идеей, ARel не является общедоступным API, и его использование может привести к непредсказуемым последствиям для вашего приложения, поэтому я советую вам не делать этого, если вы не уделите пристальное внимание развитию ARel API.
Оливье Лакан,
7
@OlivierLacan Arel - публичный API, или, точнее, он предоставляет его. Active Record просто предоставляет удобные методы, которые используют его под капотом, вполне допустимо использовать его самостоятельно. Он следует семантическому версионированию, поэтому если вы не меняете основные версии (3.xx => 4.xx), вам не нужно беспокоиться о критических изменениях.
Серый
41

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

Person.where(name: ["John", "Steve"])
daino3
источник
2
Проверку вопрос Джон против имени и Смита против LastName
steven_noble
11
@steven_noble - поэтому я выделил «тот же столбец» с помощью запроса или. Я могу полностью удалить комментарий, но подумал, что это будет полезно для людей, которые "просматривают" или "задают ТАК вопросы.
daino3
38

Обновление для 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)
)
kissrobber
источник
5
это действительно единственный чистый ответ на вопрос ОП с точки зрения ресурсов и метода. другие ответы либо (а) требуют стороннего API, либо (б) требуют интерполяции orили других операторов внутри текстового блока, ни один из которых не отражен в коде ОП.
allenwlee
6
вот приличная статья объясняя это coderwall.com/p/dgv7ag/...
allenwlee
4
Используется ...where_values.join(' OR ')в моем случае
створка
2
Вы можете опустить .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }деталь, если в ваших областях нет переменных, которые необходимо связать.
фатухоку
22

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

Person.where((:name => "John") | (:lastname => "Smith"))
Симон Перепелица
источник
3
+1. Преемником MetaWhere является squeel одним и тем же автором.
Тило
22

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

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

codenamev
источник
2
Какая версия или Rails вам нужна для этого?
Саша
3
Последнее обсуждение и запрос на извлечение, которые превратят это в ядро ​​Rails, можно найти здесь: github.com/rails/rails/pull/16052 Rails 5.0 предназначен для официального выпуска .orфункциональности цепочки. Я смог использовать патч обезьяны, который я упоминал в Rails> = 3.2; однако вы можете подождать, пока Rails 5 внесет какие-либо давние изменения в ваш код.
кодовое имя
1
Да, это было объединено и выпущено с Rails 5.
amoebe
10

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

{ where("name = ? or name = ?", a, b) }
Жолт
источник
8

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

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

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

dhulihan
источник
8

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

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

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

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

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Кристиан Фаззини
источник
5
Не поддерживается в Rails 4.2.5
фатухоку
1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')не работает. Это должно быть Foo.where (foo: 'bar1'). Or.where (Foo.where (bar: 'bar2'))
Ана Мария Мартинес Гомес
6

Рельсы 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))
genkilabs
источник
4

Рельсы 4

scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
Abs
источник
4

Если вы хотите предоставить область (вместо явной работы над всем набором данных), вот что вы должны сделать с 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
thisismydesign
источник
Это правильный, но более буквальный ответ, чем тот же, что технически делает stackoverflow.com/a/42894753/1032882 .
RudyOnRails
3

Если вы не можете выписать предложение where вручную, чтобы включить оператор "или" (т. Е. Хотите объединить две области), вы можете использовать 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 .

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

HashFail
источник
2

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

Для рельсов 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 (: или) добавит или выражение между областью действия по умолчанию и вашими данными. Это плохо.

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

HeyZiko
источник
1

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

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

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
boulder_ruby
источник
0

Таким образом, ответ на первоначальный вопрос, можете ли вы присоединиться к областям действия с «или» вместо «и», кажется «нет, вы не можете». Но вы можете написать код совершенно другой области или запроса, который выполняет эту работу, или использовать другую среду из 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.

Джон Смолл
источник
-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
coloradoblue
источник