Состояние ActiveRecord Arel OR
Как вы можете комбинировать 2 разных условия, используя логический ИЛИ вместо AND?
ПРИМЕЧАНИЕ. 2 условия генерируются как области рельсов и не могут быть легко изменены на что-то вроде where("x or y")
.
Простой пример:
admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)
Легко применить условие И (что для этого конкретного случая бессмысленно):
(admins.merge authors).to_sql
#=> select ... from ... where kind = 'admin' AND kind = 'author'
Но как вы можете создать следующий запрос, имеющий уже два разных отношения Arel?
#=> select ... from ... where kind = 'admin' OR kind = 'author'
Кажется (в соответствии с Arel readme):
Оператор OR еще не поддерживается
Но я надеюсь, что здесь это не применимо и ожидайте написать что-то вроде:
(admins.or authors).to_sql
Ответы
Ответ 1
Я немного опаздываю на вечеринку, но здесь лучшее предложение, которое я мог бы придумать:
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'))"
Ответ 2
Запросы ActiveRecord - это ActiveRecord::Relation
объекты (которые безумно не поддерживают or
), а не объекты Arel (которые делают).
[ UPDATE: с Rails 5, или поддерживается в ActiveRecord::Relation
; см. fooobar.com/questions/54476/...]
Но, к счастью, их метод 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
для выполнения фактического вызова базы данных.
Ответ 3
Из фактическая страница isl:
Оператор OR работает следующим образом:
users.where(users[:name].eq('bob').or(users[:age].lt(25)))
Ответ 4
По 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')
Ответ 5
Я столкнулся с той же проблемой, ища альтернативу activerecord для mongoid #any_of
.
Ответ на @jswanner - это хорошо, но будет работать только в том случае, если параметры - это хеш:
> 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 запросов.
Ответ 6
Просто создайте область для вашего условия ИЛИ:
scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
Ответ 7
Используя SmartTuple, он будет выглядеть примерно так:
tup = SmartTuple.new(" OR ")
tup << {:kind => "admin"}
tup << {:kind => "author"}
User.where(tup.compile)
ИЛИ
User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile)
Вы можете подумать, что я предвзятый, но я все еще считаю, что операции с традиционной структурой данных гораздо более понятные и удобны, чем цепочка методов в данном конкретном случае.
Ответ 8
Чтобы расширить ответ jswanner (который на самом деле является прекрасным решением и помог мне) для пользователей 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))
Ответ 9
Как насчет этого подхода: http://guides.rubyonrails.org/active_record_querying.html#hash-conditions (и проверьте 2.3.3)
admins_or_authors = User.where(:kind => [:admin, :author])
Ответ 10
К сожалению, он не поддерживается изначально, поэтому нам нужно взломать здесь.
И взлома выглядит так: это довольно неэффективный 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 областей.
Я выбрал первый, пока не увижу, как он работает.
В любом случае, вы должны быть очень осторожны с ним и наблюдать за созданным SQL.