ActiveRecord Arel lub warunek

Jak można połączyć 2 różne warunki używając logicznego OR zamiast AND?

Uwaga: 2 warunki są generowane jako zakresy rails i nie można ich łatwo zmienić bezpośrednio w coś w rodzaju where("x or y").

Prosty przykład:

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

Jest łatwy do zastosowania i warunek (który dla tego konkretnego przypadku jest bez znaczenia):

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

Ale w jaki sposób można przedstawić następujące zapytanie o 2 różne relacje Arel już dostępne?

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

Wydaje się (według Arel readme):

Operator OR nie jest jeszcze obsługiwany

Ale mam nadzieję, że tu się nie zastosuje i spodziewam się napisać coś w stylu:

(admins.or authors).to_sql
Author: Dmytrii Nagirniak, 2011-11-02

10 answers

Jestem trochę spóźniony na imprezę, ale oto najlepsza sugestia, jaką mogłem wymyślić:

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'))"
 70
Author: jswanner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-24 01:38:37

Zapytania ActiveRecord są obiektami ActiveRecord::Relation (które nie obsługują or), a nie obiektami Arel (które obsługują).

[ UPDATE : od Rails 5," or " jest obsługiwane w ActiveRecord::Relation; Zobacz https://stackoverflow.com/a/33248299/190135 ]

Ale na szczęście ich metoda where akceptuje obiekty Arel query. Więc jeśli User < ActiveRecord::Base...

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

query.to_sql Teraz pokazuje uspokajające:

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

Dla jasności, możesz wyodrębnić tymczasowe zapytanie częściowe zmienne:

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

I oczywiście, gdy już masz zapytanie, możesz użyć query.all do wykonania rzeczywistego wywołania bazy danych.

 89
Author: AlexChaffee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-15 19:38:51

From the actual Arel page:

Operator OR działa tak:

users.where(users[:name].eq('bob').or(users[:age].lt(25)))
 8
Author: Dave Newton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-02 06:02:02

Od Rails 5 mamy ActiveRecord::Relation#or, pozwalając ci to zrobić:

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

...który zostanie przetłumaczony na sql, którego można się spodziewać:

>> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql
SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin')
 7
Author: pje,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-20 23:03:19

Napotkałem ten sam problem, szukając alternatywy activerecord dla mongoida #any_of.

@ jswanner odpowiedź jest dobra, ale będzie działać tylko wtedy, gdy parametry where są Hashem:

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

Aby móc używać zarówno łańcuchów, jak i skrótów, możesz użyć tego :

q1 = User.where( "email = 'foo'" )
q2 = User.where( email: 'bar' )
User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) )
Rzeczywiście, to jest brzydkie, i nie chcesz tego używać na co dzień. Oto kilka #any_of implementacji, które wykonałem: https://gist.github.com/oelmekki/5396826

It let do that :

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

Edit: od tego czasu opublikowałem klejnot, który pomoże w budowaniu lub zapytaniach.

 3
Author: Olivier El Mekki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-01 11:01:15

Po prostu zrób zakres dla swojego stanu OR:

scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
 1
Author: Unixmonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-02 20:36:53

Używając SmartTuple będzie wyglądać mniej więcej tak:

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

Lub

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

Możesz myśleć, że jestem stronniczy, ale nadal uważam, że tradycyjne operacje struktury danych są o wiele bardziej przejrzyste i wygodne niż łączenie metod w tym konkretnym przypadku.

 0
Author: Alex Fortuna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-03 09:45:12

Do rozszerzenia jswanner answer (co w sumie jest zajebistym rozwiązaniem i mi pomogło) do googlowania ludzi:

Możesz zastosować taki zakres

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
Author: equivalent8,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:06

Co z tym podejściem: http://guides.rubyonrails.org/active_record_querying.html#hash-conditions (i sprawdzenie 2.3.3)

admins_or_authors = User.where(:kind => [:admin, :author])
 -2
Author: Sjors Branderhorst,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-16 11:21:02

Niestety nie jest obsługiwany natywnie, więc musimy się tutaj włamać.

A hack wygląda tak, czyli dość nieefektywny SQL (mam nadzieję, że DBAs na niego nie patrzy: -)):

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)

To generuje subselets.

A trochę lepszy hack (z perspektywy SQL) wygląda tak:

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>

To generuje właściwy lub warunek, ale oczywiście bierze pod uwagę tylko część zakresu WHERE.

Wybrałem pierwszą, dopóki nie zobaczę, jak to występuje.

W każdym razie, trzeba być bardzo ostrożnym z nim i oglądać SQL generowane.

 -4
Author: Dmytrii Nagirniak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-11 08:28:58