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


137

Estoy usando Rails3, ActiveRecord

Solo me pregunto cómo puedo encadenar los ámbitos con declaraciones OR en lugar de AND.

p.ej

Person.where(:name => "John").where(:lastname => "Smith")

Eso normalmente regresa:

name = 'John' AND lastname = 'Smith'

pero me gustaría:

`name = 'John' OR lastname = 'Smith'

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


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


48

Use ARel

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)

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"])

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

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

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.


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

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 .


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

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

4

Carriles 4

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

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

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.


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.


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

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

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.


-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.