Як ланцюжок запитів на область застосування АБО замість AND?


137

Я використовую Rails3, ActiveRecord

Цікаво, як я можу зв'язати сфери застосування з операторами АБО, а не AND.

напр

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

Це зазвичай повертається:

name = 'John' AND lastname = 'Smith'

але я хотів би:

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

Можливо, допоможе: ActiveRecord АБО запит Hash notation
potashin

Відповіді:


107

Ви б зробили

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

Зараз немає іншої підтримки АБО новим синтаксисом AR3 (тобто без використання коштовного каменя).


21
і просто задля безпеки, варто висловити це як Person.where ("name =? OR прізвище =?", "John", "Smith")
CambridgeMike

6
Це все ще застосовується в Rails 4? Нічого кращого не придумали в Rails 4?
Донато

11
У Rails 4 немає змін, але в Rails 5 буде .orвбудовано .
вапна

3
це не сфери застосування, це лише 2 пункти, або дуже конкретний випадок. Що робити, якщо я хотів би ланцюжок більш складних областей, наприклад, приєднання.
miguelfg

2
За допомогою Rails 5 ви також можете зробити щось на кшталт Person.where ("name = 'John" ") або (Person.where (" прізвище =' Smith ""))
shyam


48

Використовуйте ARel

t = Person.arel_table

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

1
Хтось може прокоментувати, чи не порушується цей тип запитів у Postgres?
Олів'є Лакан

ARel повинен бути БД-агностиком.
Саймон Перепелиця

Хоча це виглядає як гарна ідея, ARel не є загальнодоступним API, і використання цього може зламати ваш додаток несподіваними способами, тому я б радив проти нього, якщо ви не звернете пильної уваги на еволюцію API ARel.
Олів'є Лакан

7
@OlivierLacan Arel - це публічний API, а точніше, він відкриває його. Активні записи просто надають зручні методи, які використовують його під кришкою, цілком справедливо використовувати його самостійно. Після цього проводиться семантична версія, тому, якщо ви не змінюєте основних версій (3.xx => 4.xx), не потрібно турбуватися про порушення змін.
Сірий

41

Просто розміщуємо синтаксис масиву для запитів того ж стовпця АБО запитів, щоб допомогти визирати.

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
це дійсно єдина чиста відповідь на питання ОП щодо ресурсів та методу. інші відповіді або (a) вимагають сторонніх apis або (b) вимагають інтерполяції orабо інших операторів в текстовому блоці, жоден з яких не відображається в коді ОП.
allenwlee

6
ось гідна стаття, яка пояснює це coderwall.com/p/dgv7ag/…
allenwlee

4
Використовується ...where_values.join(' OR ')в моєму випадку
Sash

2
Ви можете опустити .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }частину, якщо у ваших областях немає змінних, які потребують прив'язки.
фатухоку


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 вам потрібна для цього?
Sash

3
Останнє обговорення та запит на потяг, який перетворить це на ядро ​​Rails, можна знайти тут: github.com/rails/rails/pull/16052 Rails 5.0 призначений для офіційного випуску .orфункціональності ланцюга. Мені вдалося скористатись мавповим патчем, про який я згадував у Rails> = 3.2; однак ви можете зачекати, коли Rails 5 внесе будь-які давні зміни у ваш код.
codenamev

1
Так, його було об'єднано та випущено з Rails 5.
амебе

10

Для мене (Rails 4.2.5) він працює лише так:

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

8

Це був би хороший кандидат для MetaWhere, якщо ви використовуєте Rails 3.0+, але він не працює на Rails 3.1. Ви можете замість цього спробувати скрипіти . Це зроблено тим самим автором. Ось як би ви виконали ланцюжок на основі АБО:

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

Ви можете змішувати та поєднувати І / АБО, серед багатьох чудових речей .


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
fatuhoku

1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')не працює. Це має бути Foo.where (foo: 'bar1'). Or.where (Foo.where (bar: 'bar2'))
Ana María Martínez Gómez

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

Якщо ви не можете вручну виписати пункт де ввести вираз "або" (тобто ви хочете поєднати два діапазони), ви можете використовувати об'єднання:

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

(джерело: ActiveRecord Query Union )

Це поверне всі записи, що відповідають будь-якому запиту. Однак це повертає масив, а не arel. Якщо ви дійсно хочете повернути 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

Зверніть увагу на обережність статті в кінці:

Рейки 4.1+

Rails 4.1 розглядає default_scope просто як звичайну область застосування. Область за замовчуванням (якщо у вас є) включена до результату where_values, а введення (: або) додасть або виписка між областю за замовчуванням і вашими місцями. Це погано.

Щоб вирішити це, вам просто потрібно скасувати запит.


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, яка робить трохи більше, ніж вибирає, вона включає в себе замовлення 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. Це трохи сміття, але це працює.

Ні, не кажіть мені, що я можу використовувати "проти: [: ім'я,: код]" у pg_search, я б хотів це зробити так, але поле "ім'я" - це сховище, з яким 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.