¿Cómo encadenar consultas de alcance con OR en lugar de AND?

Respuestas:

107

Tu harías

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

En este momento, no hay ningún otro soporte O por la nueva sintaxis AR3 (es decir, sin usar alguna gema de terceros).

Pedro
fuente
21
y solo por seguridad, vale la pena expresar esto como Person.where ("name =? OR lastname =?", 'John', 'Smith')
CambridgeMike
66
¿Esto todavía es aplicable en Rails 4? ¿Nada mejor surgió en Rails 4?
Donato
11
No hay cambios en Rails 4, pero Rails 5 se habrá .orincorporado .
Lima
3
estos no son ámbitos, son solo cláusulas 2 donde, o un caso de ámbito muy específico. ¿Qué pasaría si quisiera encadenar ámbitos más complejos, con uniones, por ejemplo?
miguelfg
2
Con Rails 5 también puede hacer algo como Person.where ("name = 'John'"). O (Person.where ("lastname = 'Smith'"))
shyam
59

De acuerdo con esta solicitud de extracción , Rails 5 ahora admite la siguiente sintaxis para encadenar consultas:

Post.where(id: 1).or(Post.where(id: 2))

También hay un backport de la funcionalidad en Rails 4.2 a través de esta gema.

Ryan Leaf
fuente
48

Use ARel

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)
Dan McNevin
fuente
1
¿Alguien puede comentar si este tipo de consulta se rompe en Postgres?
Olivier Lacan
Se supone que ARel es independiente de DB.
Simon Perepelitsa
Si bien esto parece una buena idea, ARel no es una API pública y su uso puede dañar su aplicación de manera inesperada, por lo que desaconsejaría a menos que preste mucha atención a la evolución de la API de ARel.
Olivier Lacan
77
@OlivierLacan Arel es una API pública, o más precisamente, expone una. Active Record solo proporciona métodos convenientes que lo usan debajo del capó, es completamente válido usarlo solo. Sigue las versiones semánticas, por lo que a menos que esté cambiando las versiones principales (3.xx => 4.xx), no hay necesidad de preocuparse por romper los cambios.
Gris
41

Simplemente publique la sintaxis de la matriz para la misma columna O consultas para ayudar a mirar.

Person.where(name: ["John", "Steve"])
daino3
fuente
2
La pregunta verifica a John contra el nombre y Smith contra el apellido
steven_noble
11
@steven_noble: es por eso que agregué "la misma columna" con la consulta o. Puedo eliminar el comentario por completo, pero pensé que sería útil para las personas que leen "o" consultan preguntas SO.
daino3
38

Actualización para Rails4

no requiere gemas de terceros

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

Vieja respuesta

no requiere gemas de terceros

Person.where(
    Person.where(:name => "John").where(:lastname => "Smith")
      .where_values.reduce(:or)
)
Kissrobber
fuente
55
Esta es realmente la única respuesta pura a la pregunta del OP en términos de recursos y método. las otras respuestas (a) requieren apis de terceros o (b) requieren oroperadores de interpolación u otros dentro de un bloque de texto, ninguno de los cuales se refleja en el código de OP.
allenwlee
66
Aquí hay un artículo decente que lo explica coderwall.com/p/dgv7ag/…
allenwlee
44
Utilizado ...where_values.join(' OR ')en mi caso
Sash
2
Puede omitir la .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }parte si sus ámbitos no tienen ninguna variable que deba vincularse.
fatuhoku
22

También puedes usar MetaWhere gema para no mezclar su código con material SQL:

Person.where((:name => "John") | (:lastname => "Smith"))
Simon Perepelitsa
fuente
3
+1. El sucesor de MetaWhere es chillido por el mismo autor.
Thilo
22

En caso de que alguien esté buscando una respuesta actualizada a esta, parece que hay una solicitud de extracción existente para obtener esto en Rails: https://github.com/rails/rails/pull/9052 .

Gracias al parche de mono de @ j-mcnally para ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) puede hacer lo siguiente:

Person.where(name: 'John').or.where(last_name: 'Smith').all

Aún más valiosa es la capacidad de encadenar ámbitos con 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) }

Luego puede encontrar todas las personas con nombre o apellido o cuyo padre con apellido

Person.first_or_last_name('John Smith').or.parent_last_name('Smith')

No es el mejor ejemplo para el uso de esto, pero solo trato de encajar con la pregunta.

codenamev
fuente
2
¿Qué versión o Rails necesitas para esto?
Faja
3
La discusión más reciente y la solicitud de extracción que lo convertirán en el núcleo de Rails se pueden encontrar aquí: github.com/rails/rails/pull/16052 Rails 5.0 está destinado al lanzamiento oficial de la funcionalidad de la .orcadena. Pude usar el parche de mono que mencioné en Rails> = 3.2; sin embargo, es posible que desee esperar a que Rails 5 introduzca cualquier cambio de larga data en su código.
codenamev
1
Sí, se fusionó y se lanzó con Rails 5.
2016
10

Para mí (Rails 4.2.5) solo funciona así:

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

Este sería un buen candidato para MetaWhere si está utilizando Rails 3.0+, pero no funciona en Rails 3.1. Es posible que desee probar chirrido en su lugar. Está hecho por el mismo autor. Así es como realizarías una cadena basada en OR:

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

Puedes mezclar y combinar AND / OR, entre muchas otras cosas increíbles .

dhulihan
fuente
8

Una versión actualizada de Rails / ActiveRecord puede admitir esta sintaxis de forma nativa. Se vería similar a:

Foo.where(foo: 'bar').or.where(bar: 'bar')

Como se señaló en esta solicitud de extracción https://github.com/rails/rails/pull/9052

Por ahora, simplemente seguir con lo siguiente funciona muy bien:

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Christian Fazzini
fuente
55
No es compatible con Rails 4.2.5
fatuhoku
1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')No funciona. Debería ser Foo.where (foo: 'bar1'). Or.where (Foo.where (bar: 'bar2'))
Ana María Martínez Gómez
6

Rieles 4 + Alcance + Arel

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

Traté de encadenar ámbitos con nombre con. O sin suerte, pero esto funcionó para encontrar algo con esos conjuntos booleanos. Genera SQL como

SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
genkilabs
fuente
4

Carriles 4

scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
Abdominales
fuente
4

Si está buscando proporcionar un alcance (en lugar de trabajar explícitamente en todo el conjunto de datos), esto es lo que debe hacer con Rails 5:

scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }

O:

def self.john_or_smith
  where(name: "John").or(where(lastname: "Smith"))
end
este es mi diseño
fuente
Esto es correcto, pero es una respuesta más literal que la misma cosa que técnicamente hace stackoverflow.com/a/42894753/1032882 .
RudyOnRails
3

Si no puede escribir la cláusula where manualmente para incluir la declaración "o" (es decir, desea combinar dos ámbitos), puede usar union:

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

(fuente: ActiveRecord Query Union )

Esto devolverá todos los registros que coincidan con cualquiera de las consultas. Sin embargo, esto devuelve una matriz, no un arel. Si realmente desea devolver un arel, consulte esta esencia: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

Esto hará el trabajo, siempre y cuando no le importe los rieles de parches de mono.

HashFail
fuente
2

También vea estas preguntas relacionadas: aquí , aquí y aquí

Para los rieles 4, según este artículo y esta respuesta original

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

Tenga en cuenta la precaución del artículo al final:

Carriles 4.1+

Rails 4.1 trata default_scope solo como un alcance regular. El alcance predeterminado (si tiene alguno) se incluye en el resultado where_values ​​e inyectar (: o) agregará una declaración entre el alcance predeterminado y su ubicación. Eso es malo.

Para resolver eso, solo necesita quitar el alcance de la consulta.

Hola ziko
fuente
1

Esta es una forma muy conveniente y funciona bien en 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")
  )
Jigar Bhatt
fuente
0

la squeelgema proporciona una manera increíblemente fácil de lograr esto (antes de esto, usé algo como el método de @ coloradoblue):

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
boulder_ruby
fuente
0

Entonces, la respuesta a la pregunta original, ¿puede unir ámbitos con 'o' en lugar de 'y' parece ser "no, no puede". Pero puede codificar manualmente un alcance o consulta completamente diferente que haga el trabajo, o usar un marco diferente de ActiveRecord, por ejemplo, MetaWhere o Squeel. No es útil en mi caso

Estoy 'o' creando un alcance generado por pg_search, que hace un poco más que seleccionar, incluye el orden de ASC, lo que hace un desastre de una unión limpia. Quiero 'o' con un alcance artesanal que hace cosas que no puedo hacer en pg_search. Así que tuve que hacerlo así.

Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")

Es decir, convertir los ámbitos en sql, poner corchetes alrededor de cada uno, unirlos y luego find_by_sql utilizando el sql generado. Es un poco basura, pero funciona.

No, no me digas que puedo usar "contra: [: nombre,: código]" en pg_search, me gustaría hacerlo así, pero el campo 'nombre' es un hstore, que pg_search no puede manejar todavía. Por lo tanto, el alcance por nombre debe ser diseñado a mano y luego unido con el alcance pg_search.

John Small
fuente
-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
coloradoblue
fuente