ActiveRecord Arel ИЛИ условие - PullRequest
56 голосов
/ 02 ноября 2011

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

ПРИМЕЧАНИЕ: 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'

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

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

Кажется ( согласно Readme Arel ):

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

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

(admins.or authors).to_sql

Ответы [ 10 ]

91 голосов
/ 07 декабря 2012

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 для выполнения фактического вызова базы данных.

70 голосов
/ 23 февраля 2012

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

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'))"
9 голосов
/ 02 ноября 2011

со фактической страницы арла :

Оператор ИЛИ работает так:

users.where(users[:name].eq('bob').or(users[:age].lt(25)))
8 голосов
/ 21 октября 2015

Начиная с 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')
3 голосов
/ 16 апреля 2013

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

. *1002*

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

* 1005.*

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

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

Редактировать: с тех пор я опубликовал гем дляпомогите построить ИЛИ запросы .

1 голос
/ 02 ноября 2011

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

scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
0 голосов
/ 10 декабря 2012

Чтобы продлить 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))
0 голосов
/ 16 марта 2012

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

admins_or_authors = User.where(:kind => [:admin, :author])
0 голосов
/ 11 ноября 2011

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

И хак выглядит так, что довольно неэффективный 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>

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

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

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

0 голосов
/ 02 ноября 2011

Использование 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)

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

...