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


137

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

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

например

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

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

name = 'John' AND lastname = 'Smith'

но я бы хотел:

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

Может быть
полезным

Ответы:


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

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

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

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


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, среди многих других удивительных вещей .


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

4

Рельсы 4

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

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

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

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


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

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


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

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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.