Условие ActiveRecord Arel ИЛИ

Как вы можете объединить 2 разных условия, используя логическое ИЛИ вместо И?

ПРИМЕЧАНИЕ. 2 условия генерируются как области действия рельсов и не могут быть легко изменены напрямую на что-то вроде where("x or y").

Простой пример:

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

Легко применить условие AND (которое в данном конкретном случае бессмысленно):

(admins.merge authors).to_sql
#=> select ... from ... where kind = 'admin' AND kind = 'author'

Но как вы можете создать следующий запрос, имея уже 2 разных отношения Arel?

#=> select ... from ... where kind = 'admin' OR kind = 'author'

Кажется (согласно ридми Arel):

Оператор OR пока не поддерживается

Но я надеюсь, что это не применимо здесь, и ожидаю написать что-то вроде:

(admins.or authors).to_sql

person Dmytrii Nagirniak    schedule 02.11.2011    source источник
comment
Может помочь: ActiveRecord ИЛИ запрос Hash notation   -  person potashin    schedule 02.07.2016


Ответы (10)


Я немного опоздал на вечеринку, но вот лучшее предложение, которое я мог придумать:

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

admins = admins.where_values.reduce(:and)
authors = authors.where_values.reduce(:and)

User.where(admins.or(authors)).to_sql
# => "SELECT \"users\".* FROM \"users\"  WHERE ((\"users\".\"kind\" = 'admin' OR \"users\".\"kind\" = 'author'))"
person jswanner    schedule 23.02.2012
comment
Это довольно мило на самом деле. Спасибо. - person Dmytrii Nagirniak; 24.02.2012
comment
Это давало мне ошибку undefined method .or for [string], но #{admins} или #{authors} работали отлично. - person bhaibel; 31.08.2012
comment
bhaibel: У меня была такая же проблема - она ​​была вызвана соединением. Вместо этого я использую объединенную модель, и больше нет строковых ошибок. - person tomaszbak; 14.09.2012
comment
вы можете пропустить шаг where_values.reduce, если начнете с настоящего запроса ARel... см. мой ответ - person AlexChaffee; 07.12.2012
comment
Это не работает должным образом (rails 3.2.12), если ваши области действия имеют более одного условия. Проблема в том, что круглые скобки не помещаются вокруг условий ИЛИ, в результате чего операции И влияют на весь запрос, а не на его часть. - person BM5k; 29.05.2013
comment
Отличный совет! Спасибо. - person Blue Smith; 15.07.2013

Запросы ActiveRecord — это объекты ActiveRecord::Relation (которые до безумия не поддерживают or), а не объекты Arel (которые поддерживают).

[ ОБНОВЛЕНИЕ: начиная с Rails 5, "или" поддерживается в ActiveRecord::Relation; см. https://stackoverflow.com/a/33248299/190135]

Но, к счастью, их метод where принимает объекты запросов ARel. Итак, если User < ActiveRecord::Base...

users = User.arel_table
query = User.where(users[:kind].eq('admin').or(users[:kind].eq('author')))

query.to_sql теперь показывает обнадеживающее:

SELECT "users".* FROM "users"  WHERE (("users"."kind" = 'admin' OR "users"."kind" = 'author'))

Для ясности вы можете извлечь некоторые временные переменные частичного запроса:

users = User.arel_table
admin = users[:kind].eq('admin')
author = users[:kind].eq('author')
query = User.where(admin.or(author))

И, естественно, когда у вас есть запрос, вы можете использовать query.all для выполнения фактического вызова базы данных.

person AlexChaffee    schedule 07.12.2012
comment
Это сработало и для меня. Теперь, когда выпущен Rails 4, это все еще лучший способ получить условие ИЛИ? - person Sathish; 11.07.2013

Начиная с Rails 5 у нас есть ActiveRecord::Relation#or, что позволяет вам сделать это:

User.where(kind: :author).or(User.where(kind: :admin))

... который переводится в sql, который вы ожидаете:

>> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql
SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin')
person pje    schedule 20.10.2015
comment
Это хорошо работает, чтобы получить несколько идентификаторов, основанных на разных поисках в сложной таблице, включая Postgesql, где LIKE не работает с целыми числами. - person Gary; 31.03.2017

На настоящей странице:

Оператор ИЛИ работает следующим образом:

users.where(users[:name].eq('bob').or(users[:age].lt(25)))
person Dave Newton    schedule 02.11.2011
comment
Я видел это. Это еще НЕ поддерживается. Как оно отвечает на вопрос? - person Dmytrii Nagirniak; 02.11.2011
comment
Не удалось отформатировать его в комментарии. Есть тесты для операторов ИЛИ, но страница, на которую вы ссылаетесь, относится к 2009 году и на самом деле не используется AREL. Он может не отвечать на вопрос, но, по крайней мере, это правильная ссылка и не говорится, что он не поддерживается. - person Dave Newton; 02.11.2011
comment
В порядке. Но вы не можете сделать это с помощью областей ActiveRecord Rails. Вы пробовали пример с администраторами и авторами или подобным? На ActiveRecord::Relation нет метода or. Преобразование его в Arel дает другой набор проблем (запрос SelectManager, а не Where). Или я что-то пропустил? - person Dmytrii Nagirniak; 02.11.2011
comment
О, я думал, что вы имели в виду are, потому что вы связались с ним - извините. - person Dave Newton; 02.11.2011
comment
Насколько я знаю, вы можете передать условия arel методам AR, так что это должно работать: User.where(users[:name].eq('bob').or(users[:age].lt(25))) - person tokland; 02.11.2011
comment
@tokland, а как объединить две существующие реальности с помощью ИЛИ? - person Dmytrii Nagirniak; 02.11.2011
comment
@Dmytrii: насколько я знаю, это невозможно, ИЛИ должны быть в одном и том же выражении. Для меня это имеет смысл, области действия накопительные, поэтому И подходит, а ИЛИ нет. - person tokland; 02.11.2011

Я столкнулся с той же проблемой, ища альтернативу активной записи монгоиду #any_of.

Ответ @jswanner хорош, но будет работать только в том случае, если параметры where являются хэшем:

> User.where( email: 'foo', first_name: 'bar' ).where_values.reduce( :and ).method( :or )                                                
=> #<Method: Arel::Nodes::And(Arel::Nodes::Node)#or>

> User.where( "email = 'foo' and first_name = 'bar'" ).where_values.reduce( :and ).method( :or )                                         
NameError: undefined method `or' for class `String'

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

q1 = User.where( "email = 'foo'" )
q2 = User.where( email: 'bar' )
User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) )

Действительно, это уродливо, и вы не хотите использовать это ежедневно. Вот некоторая #any_of реализация, которую я сделал: https://gist.github.com/oelmekki/5396826

Это позволяет сделать это:

> q1 = User.where( email: 'foo1' ); true                                                                                                 
=> true

> q2 = User.where( "email = 'bar1'" ); true                                                                                              
=> true

> User.any_of( q1, q2, { email: 'foo2' }, "email = 'bar2'" )
User Load (1.2ms)  SELECT "users".* FROM "users" WHERE (((("users"."email" = 'foo1' OR (email = 'bar1')) OR "users"."email" = 'foo2') OR (email = 'bar2')))

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

person kik    schedule 16.04.2013

Просто создайте область действия для вашего условия ИЛИ:

scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
person Unixmonkey    schedule 02.11.2011
comment
Ну, ваш запрос не является правильным SQL :) Но то, что вы предложили, это именно то, чего я не могу сделать. Пожалуйста, прочитайте вопрос. Теперь вы видите примечание, выделенное жирным шрифтом? - person Dmytrii Nagirniak; 03.11.2011
comment
@DmytriiNagirniak Исправлена ​​ошибка sql, но я все еще не понимаю, почему это не сработает для вас. Может быть, это связано с Arel (в основном я все еще использую 2.3), или, может быть, вопрос нуждается в дополнительных разъяснениях. - person Unixmonkey; 03.11.2011
comment
Это сработает. Но, как я уже сказал в своем вопросе, у меня есть 2 прицела. Мне нужно объединить те, которые используют оператор OR. Я не могу переписать эти две области видимости в один оператор where. - person Dmytrii Nagirniak; 03.11.2011

При использовании SmartTuple это будет выглядеть примерно так:

tup = SmartTuple.new(" OR ")
tup << {:kind => "admin"}
tup << {:kind => "author"}
User.where(tup.compile)

OR

User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile)

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

person Alex Fortuna    schedule 02.11.2011
comment
Можете ли вы легко преобразовать области видимости в SmartTuple? Я спрашиваю, потому что приложение уже активно использует Arel. - person Dmytrii Nagirniak; 03.11.2011
comment
Нет, но вы можете возвращать объекты SmartTuple из своего кода вместо возврата областей, и затем быстро преобразовывать SmartTuple в области при необходимости. - person Alex Fortuna; 03.11.2011

Чтобы расширить jswanner answer (что на самом деле является отличным решением и помогло мне) для поиска людей в Google:

вы можете применить область, подобную этой

scope :with_owner_ids_or_global, lambda{ |owner_class, *ids|
  with_ids = where(owner_id: ids.flatten).where_values.reduce(:and)
  with_glob = where(owner_id: nil).where_values.reduce(:and)
  where(owner_type: owner_class.model_name).where(with_ids.or( with_glob ))
}

User.with_owner_ids_or_global(Developer, 1, 2)
# =>  ...WHERE `users`.`owner_type` = 'Developer' AND ((`users`.`owner_id` IN (1, 2) OR `users`.`owner_id` IS NULL))
person equivalent8    schedule 10.12.2012

Как насчет этого подхода: http://guides.rubyonrails.org/active_record_querying.html#hash-conditions (и проверьте 2.3.3)

admins_or_authors = User.where(:kind => [:admin, :author])
person Sjors Branderhorst    schedule 16.03.2012
comment
Пожалуйста, прочитайте вопрос. Созданы 2 отношения рельсов, которые нельзя контролировать. - person Dmytrii Nagirniak; 17.03.2012
comment
Правда, надо было лучше прочитать вопрос. Вы конкретно заявляете: «Но как вы можете создать следующий запрос, имея уже 2 разных отношения Arel?», и я не отвечаю на это. И действительно, когда OR-ing области условий не поддерживаются, вам нужно прибегнуть к хакам. Так что извинения в порядке вещей. Я просто хотел указать, что может быть более простое решение, которое поможет вам избежать проблемы. - person Sjors Branderhorst; 19.03.2012

К сожалению, это не поддерживается изначально, поэтому нам нужно взломать здесь.

И хак выглядит так, что довольно неэффективный SQL (надеюсь, администраторы баз данных не смотрят на это :-)):

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

both = User.where("users.id in (#{admins.select(:id)}) OR users.id in (#{authors.select(:id)})")
both.to_sql # => where users.id in (select id from...) OR users.id in (select id from)

Это генерирует подмножества.

И немного лучший хак (с точки зрения SQL) выглядит так:

admins_sql = admins.arel.where_sql.sub(/^WHERE/i,'')
authors_sql = authors.arel.where_sql.sub(/^WHERE/i,'')
both = User.where("(#{admins_sql}) OR (#{authors_sql})")
both.to_sql # => where <admins where conditions> OR <authors where conditions>

Это создает правильное условие ИЛИ, но, очевидно, учитывает только часть WHERE областей.

Я выбрал 1-й, пока не посмотрю, как он работает.

В любом случае, вы должны быть очень осторожны с ним и смотреть сгенерированный SQL.

person Dmytrii Nagirniak    schedule 11.11.2011
comment
Честно говоря, это работает, но, составляя sql с такими регулярными выражениями, мне просто пришлось проголосовать за ваш ответ. Тогда вы можете просто написать sql и использовать find_by_sql и пропустить весь слой Arel. - person Sjors Branderhorst; 16.03.2012
comment
Подумайте, чтобы показать лучшие решения тогда. Я предоставил 2, один из которых мне не нравится и я им не пользуюсь. - person Dmytrii Nagirniak; 17.03.2012
comment
Три человека дали правильные ответы, используя действительный ARel, и все же вы приняли свой собственный уродливый ответ, где, как сказал Сьорс, вы могли просто манипулировать строками для создания SQL в первую очередь. Понизить. - person ches; 23.03.2013
comment
Честно говоря, Arel отказывается компилировать запросы, если у вас есть соединения на любой стороне ИЛИ, ссылаясь на операнды ИЛИ, которые должны быть структурно одинаковыми. Таким образом, в некоторых случаях взлом SQL является единственным способом заставить все это работать, поскольку Arel все еще не обеспечивает хороший динамический способ компиляции сложных SQL. - person Gabor Garami; 02.07.2017