Come concatenare le query dell'ambito con OR anziché AND?


137

Sto usando Rails3, ActiveRecord

Mi chiedo solo come posso concatenare gli ambiti con le istruzioni OR anziché AND.

per esempio

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

Che normalmente ritorna:

name = 'John' AND lastname = 'Smith'

ma vorrei:

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

Risposte:


107

Lo faresti

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

Al momento, non c'è nessun altro supporto OR da parte della nuova sintassi AR3 (ovvero senza l'utilizzo di qualche gemma di terze parti).


21
e solo per motivi di sicurezza, vale la pena esprimerlo come Person.where ("name =? OR lastname =?", 'John', 'Smith')
CambridgeMike

6
Questo è ancora applicabile in Rails 4? Niente di meglio è nato in Rails 4?
Donato,

11
Nessuna modifica in Rails 4, ma Rails 5 avrà .orincorporato .
lime,

3
non si tratta di ambiti, sono solo 2 clausole where o un caso scope molto specifico. E se volessi concatenare ambiti più complessi, ad esempio con join.
miguelfg,

2
Con Rails 5 puoi anche fare qualcosa del tipo, Person.where ("name = 'John'"). Oppure (Person.where ("lastname = 'Smith'"))
shyam,


48

Usa ARel

t = Person.arel_table

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

1
Qualcuno può commentare se questo tipo di query si interrompe in Postgres?
Olivier Lacan,

Si suppone che ARel sia indipendente da DB.
Simon Perepelitsa,

Anche se questa sembra una buona idea, ARel non è un'API pubblica e l'utilizzo di questa opzione potrebbe interrompere l'app in modo imprevisto, quindi sconsiglio di farlo a meno che non presti molta attenzione all'evoluzione dell'API ARel.
Olivier Lacan,

7
@OlivierLacan Arel è un'API pubblica o, più precisamente, ne espone una. Active Record fornisce solo metodi di praticità che lo usano sotto il cofano, è completamente valido da usare da solo. Segue il controllo delle versioni semantico, quindi a meno che non si modifichino le versioni principali (3.xx => 4.xx), non è necessario preoccuparsi di interrompere le modifiche.
Grey,

41

Pubblicando semplicemente la sintassi dell'array per la stessa colonna O query per aiutare a sbirciare.

Person.where(name: ["John", "Steve"])

2
La domanda controlla John contro il nome e Smith contro il cognome
steven_noble

11
@steven_noble - ecco perché ho messo in grassetto "stessa colonna" con la query o. Posso eliminare del tutto il commento, ma ho pensato che sarebbe stato utile per le persone che esaminano "o" le domande SO.
daino3,

38

Aggiornamento per Rails4

non richiede gemme di terze parti

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

Vecchia risposta

non richiede gemme di terze parti

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

5
questa è davvero l'unica risposta pura alla domanda del PO in termini di risorse e metodo. le altre risposte (a) richiedono API di terze parti o (b) richiedono interpolazione oro altri operatori all'interno di un blocco di testo, nessuno dei quali si riflette nel codice OP.
Allenwlee,

6
ecco un articolo decente che lo spiega coderwall.com/p/dgv7ag/…
allenwlee

4
Usato ...where_values.join(' OR ')nel mio caso
Sash

2
È possibile omettere la .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }parte se gli ambiti non hanno variabili che devono essere rilegate.
Fatuhoku,


22

Nel caso in cui qualcuno stia cercando una risposta aggiornata a questa, sembra che ci sia una richiesta pull esistente per ottenere questo in Rails: https://github.com/rails/rails/pull/9052 .

Grazie alla patch scimmia di @ j-mcnally per ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) puoi fare quanto segue:

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

Ancora più preziosa è la capacità di concatenare gli ambiti 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) }

Quindi puoi trovare tutte le persone con nome o cognome o il cui genitore con cognome

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

Non è il miglior esempio per l'uso di questo, ma sto solo cercando di adattarlo alla domanda.


2
Di quale versione o Rails hai bisogno per questo?
Sash

3
L'ultima discussione e la richiesta pull che lo trasformeranno nel core di Rails sono disponibili qui: github.com/rails/rails/pull/16052 Rails 5.0 è destinato al rilascio ufficiale della funzionalità della .orcatena. Sono stato in grado di usare la patch di scimmia che ho citato su Rails> = 3.2; tuttavia potresti voler aspettare che Rails 5 introduca eventuali modifiche di vecchia data al tuo codice.
codenamev,

1
Sì, è stato unito e rilasciato con Rails 5.
amoebe,

10

Per me (Rails 4.2.5) funziona solo in questo modo:

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

8

Questo sarebbe un buon candidato per MetaWhere se stai usando Rails 3.0+, ma non funziona su Rails 3.1. Potresti voler provare invece il tergipavimento . È stato realizzato dallo stesso autore. Ecco come eseguiresti una catena basata su OR:

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

Puoi mescolare e abbinare AND / OR, tra molte altre cose fantastiche .


8

Una versione aggiornata di Rails / ActiveRecord può supportare questa sintassi in modo nativo. Sembrerebbe simile a:

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

Come indicato in questa richiesta pull https://github.com/rails/rails/pull/9052

Per ora, semplicemente attenersi a quanto segue funziona alla grande:

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')

5
Non supportato in Rails 4.2.5
fatuhoku,

1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')non funziona. Dovrebbe essere Foo.where (foo: 'bar1'). Or.where (Foo.where (bar: 'bar2'))
Ana María Martínez Gómez

6

Rotaie 4 + Scope + 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

Ho provato a concatenare ambiti di nome con .or e senza fortuna, ma questo ha funzionato per trovare qualcosa con quei set booleani. Genera SQL come

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

4

Rotaie 4

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

4

Se stai cercando di fornire un ambito (invece di lavorare esplicitamente sull'intero set di dati) ecco cosa dovresti fare 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

Questo è corretto, ma una risposta più letterale della stessa cosa che stackoverflow.com/a/42894753/1032882 tecnicamente fa.
RudyOnRails

3

Se non riesci a scrivere manualmente la clausola where per includere l'istruzione "o" (ovvero, vuoi combinare due ambiti), puoi usare union:

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

(fonte: ActiveRecord Query Union )

Questo restituirà tutti i record corrispondenti a entrambe le query. Tuttavia, questo restituisce un array, non un arel. Se vuoi davvero restituire un arel, dai un'occhiata a questa sintesi: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

Questo farà il lavoro, a patto che non ti dispiaccia rotaie di patch scimmia.


2

Vedi anche queste domande correlate: qui , qui e qui

Per rotaie 4, basato su questo articolo e questa risposta originale

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

Nota la cautela dell'articolo alla fine:

Rotaie 4.1+

Rails 4.1 considera default_scope come un normale ambito. L'ambito predefinito (se presente) è incluso nel risultato where_values ​​e inject (: o) aggiungerà o rendiconterà tra l'ambito predefinito e la posizione dell'utente. Questo è male.

Per risolverlo, devi solo annullare l'ambito della query.


1

Questo è un modo molto conveniente e funziona benissimo in 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 squeelgemma fornisce un modo incredibilmente semplice per raggiungere questo obiettivo (prima di questo ho usato qualcosa come il metodo di @ coloradoblue):

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

0

Quindi, la risposta alla domanda originale, puoi unire gli ambiti con 'o' invece di 'e' sembra essere "no, non puoi". Ma puoi codificare manualmente un ambito o una query completamente diversa che fa il lavoro o utilizzare un framework diverso da ActiveRecord, ad esempio MetaWhere o Squeel. Non utile nel mio caso

Sto 'o' un ambito generato da pg_search, che fa un po 'più di selezionare, include l'ordine di ASC, che crea un disastro di unione pulita. Voglio "o" con un ambito artigianale che fa cose che non posso fare in pg_search. Quindi ho dovuto farlo in questo modo.

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

Vale a dire trasformare gli ambiti in sql, mettere parentesi attorno a ciascuno, unirli insieme e poi find_by_sql usando sql generato. È un po 'spazzatura, ma funziona.

No, non dirmi che posso usare "against: [: name,: code]" in pg_search, mi piacerebbe farlo in questo modo, ma il campo 'name' è un hstore, che pg_search non può gestire ancora. Quindi l'ambito per nome deve essere realizzato a mano e quindi unito all'ambito pg_search.


-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.