Wie verkette ich Bereichsabfragen mit ODER anstelle von UND?

137

Ich benutze Rails3, ActiveRecord

Ich frage mich nur, wie ich die Bereiche mit ODER-Anweisungen anstatt mit UND verketten kann.

z.B

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

Das gibt normalerweise zurück:

name = 'John' AND lastname = 'Smith'

aber ich möchte:

`name = 'John' OR lastname = 'Smith'
user410715
quelle
Kann

Antworten:

107

Du würdest

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

Derzeit gibt es keine andere ODER-Unterstützung durch die neue AR3-Syntax (dh ohne Verwendung eines Edelsteins eines Drittanbieters).

Petros
quelle
21
und nur aus Sicherheitsgründen lohnt es sich, dies als Person.where ("name =? OR lastname =?", 'John', 'Smith')
CambridgeMike
6
Dies gilt weiterhin in Rails 4? In Rails 4 ist nichts Besseres aufgetaucht?
Donato
11
Keine Änderung in Rails 4, aber Rails 5 wird .oreingebaut .
Kalk
3
Dies sind keine Bereiche, sondern nur 2 where-Klauseln oder ein ganz bestimmter Fall. Was wäre, wenn ich komplexere Bereiche verketten möchte, beispielsweise mit Joins?
Miguelfg
2
Mit Rails 5 können Sie auch so etwas wie Person.where ("name = 'John'") oder (Person.where ("lastname = 'Smith'"))
tun
59

Gemäß dieser Pull-Anforderung unterstützt Rails 5 jetzt die folgende Syntax zum Verketten von Abfragen:

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

Es gibt auch einen Backport der Funktionalität in Rails 4.2 über dieses Juwel.

Ryan Leaf
quelle
48

Verwenden Sie ARel

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)
Dan McNevin
quelle
1
Kann jemand kommentieren, ob diese Art von Abfrage in Postgres nicht funktioniert?
Olivier Lacan
ARel soll DB-agnostisch sein.
Simon Perepelitsa
Obwohl dies eine gute Idee ist, ist ARel keine öffentliche API, und die Verwendung dieser API kann Ihre App auf unerwartete Weise beschädigen. Ich würde daher davon abraten, es sei denn, Sie achten genau auf die Entwicklung der ARel-API.
Olivier Lacan
7
@OlivierLacan Arel ist eine öffentliche API, genauer gesagt, eine. Active Record bietet nur praktische Methoden, die es unter der Haube verwenden. Es ist völlig gültig, es alleine zu verwenden. Es folgt die semantische Versionierung. Wenn Sie also keine Hauptversionen ändern (3.xx => 4.xx), müssen Sie sich keine Gedanken über Änderungen machen.
Grau
41

Veröffentlichen Sie einfach die Array-Syntax für dieselbe Spalte ODER Abfragen, um einen besseren Überblick zu erhalten.

Person.where(name: ["John", "Steve"])
daino3
quelle
2
Die Frage prüft John gegen Namen und Smith gegen Nachnamen
steven_noble
11
@steven_noble - deshalb habe ich "dieselbe Spalte" mit der Abfrage oder fett gedruckt. Ich kann den Kommentar insgesamt löschen, dachte aber, er wäre hilfreich für Leute, die SO-Fragen lesen oder abfragen.
Daino3
38

Update für Rails4

erfordert keine Edelsteine ​​von Drittanbietern

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

Alte Antwort

erfordert keine Edelsteine ​​von Drittanbietern

Person.where(
    Person.where(:name => "John").where(:lastname => "Smith")
      .where_values.reduce(:or)
)
Kissrobber
quelle
5
Dies ist wirklich die einzige reine Antwort auf die Frage des OP in Bezug auf Ressourcen und Methoden. Die anderen Antworten erfordern entweder (a) Apis von Drittanbietern oder (b) Interpolation oroder andere Operatoren innerhalb eines Textblocks, von denen keiner im OP-Code wiedergegeben ist.
Allenwlee
6
Hier ist ein anständiger Artikel, der es erklärt. coderwall.com/p/dgv7ag/…
allenwlee
4
In ...where_values.join(' OR ')meinem Fall verwendet
Schärpe
2
Sie können den .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }Teil weglassen, wenn Ihre Bereiche keine Variablen enthalten, die gebunden werden müssen.
Fatuhoku
22

Sie können MetaWhere gem auch verwenden , um Ihren Code nicht mit SQL- Inhalten zu verwechseln:

Person.where((:name => "John") | (:lastname => "Smith"))
Simon Perepelitsa
quelle
3
+1. Der Nachfolger von MetaWhere ist Squeel vom selben Autor.
Thilo
22

Falls jemand nach einer aktualisierten Antwort auf diese Frage sucht, scheint eine Pull-Anfrage vorhanden zu sein, um diese in Rails zu übertragen: https://github.com/rails/rails/pull/9052 .

Dank des Affen-Patches von @ j-mcnally für ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) können Sie Folgendes tun:

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

Noch wertvoller ist die Fähigkeit, Zielfernrohre zu verketten mit 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) }

Dann finden Sie alle Personen mit Vor- oder Nachnamen oder deren Eltern mit Nachnamen

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

Nicht das beste Beispiel dafür, sondern nur der Versuch, es mit der Frage in Einklang zu bringen.

codenamev
quelle
2
Welche Version oder Rails benötigen Sie dafür?
Schärpe
3
Die neueste Diskussion und die Pull-Anfrage, die dies in den Rails-Kern aufnehmen wird, finden Sie hier: github.com/rails/rails/pull/16052 Rails 5.0 ist für die offizielle Veröffentlichung der .orKettenfunktionalität vorgesehen. Ich konnte den Affen-Patch verwenden, den ich auf Rails> = 3.2 erwähnt habe. Möglicherweise möchten Sie jedoch warten, bis Rails 5 langjährige Änderungen an Ihrem Code vorgenommen hat.
Codenamev
1
Ja, es wurde zusammengeführt und mit Rails 5 veröffentlicht.
Amoebe
10

Bei mir (Rails 4.2.5) funktioniert das nur so:

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

Dies wäre ein guter Kandidat für MetaWhere, wenn Sie Rails 3.0+ verwenden, aber es funktioniert nicht auf Rails 3.1. Vielleicht möchten Sie stattdessen Squeel ausprobieren . Es ist vom selben Autor gemacht. So würden Sie eine OP-basierte Kette durchführen:

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

Sie können UND / ODER unter vielen anderen fantastischen Dingen kombinieren .

Dhulihan
quelle
8

Eine aktualisierte Version von Rails / ActiveRecord unterstützt diese Syntax möglicherweise nativ. Es würde ähnlich aussehen wie:

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

Wie in dieser Pull-Anfrage angegeben, https://github.com/rails/rails/pull/9052

Im Moment funktioniert es großartig, einfach bei Folgendem zu bleiben:

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Christian Fazzini
quelle
5
Nicht unterstützt in Rails 4.2.5
Fatuhoku
1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')funktioniert nicht. Es sollte Foo.where (foo: 'bar1') oder orwhere (Foo.where (bar: 'bar2')) sein
Ana María Martínez Gómez
6

Schienen 4 + Umfang + 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

Ich habe versucht, benannte Bereiche mit .oder und ohne Glück zu verketten, aber dies hat dazu beigetragen, mit diesen Booleschen Werten etwas zu finden. Erzeugt SQL wie

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

Schienen 4

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

Wenn Sie einen Bereich bereitstellen möchten (anstatt explizit am gesamten Datensatz zu arbeiten), sollten Sie Folgendes mit Rails 5 tun:

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

Oder:

def self.john_or_smith
  where(name: "John").or(where(lastname: "Smith"))
end
Das ist mein Design
quelle
Dies ist richtig, aber eine wörtlichere Antwort als das, was stackoverflow.com/a/42894753/1032882 technisch tut.
RudyOnRails
3

Wenn Sie die where-Klausel nicht manuell ausschreiben können, um die Anweisung "oder" einzuschließen (dh Sie möchten zwei Bereiche kombinieren), können Sie union verwenden:

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

(Quelle: ActiveRecord Query Union )

Dadurch werden alle Datensätze zurückgegeben, die mit einer der beiden Abfragen übereinstimmen. Dies gibt jedoch ein Array zurück, kein Arel. Wenn Sie wirklich ein Arel zurückgeben möchten, überprüfen Sie diese Liste: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

Dies wird den Job erledigen, solange es Ihnen nichts ausmacht, Affen-Patching-Schienen zu verwenden.

HashFail
quelle
2

Siehe auch diese verwandten Fragen: hier , hier und hier

Für Schienen 4, basierend auf diesem Artikel und dieser Originalantwort

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

Beachten Sie die Vorsicht des Artikels am Ende:

Schienen 4.1+

Rails 4.1 behandelt default_scope nur als regulären Bereich. Der Standardbereich (falls vorhanden) ist im where_values-Ergebnis enthalten, und injizieren (: oder) fügt oder Anweisung zwischen dem Standardbereich und Ihrem wherees hinzu. Das ist schlecht.

Um dies zu lösen, müssen Sie nur die Abfrage deaktivieren.

HeyZiko
quelle
1

Dies ist eine sehr bequeme Methode und funktioniert in Rails 5 einwandfrei:

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
quelle
0

Der squeelEdelstein bietet eine unglaublich einfache Möglichkeit, dies zu erreichen (zuvor habe ich so etwas wie die Methode von @ coloradoblue verwendet):

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

Die Antwort auf die ursprüngliche Frage: Können Sie Bereiche mit 'oder' anstelle von 'verbinden und' scheint 'nein, Sie können nicht' zu sein. Sie können jedoch einen völlig anderen Bereich oder eine andere Abfrage codieren, die den Job ausführt, oder ein anderes Framework als ActiveRecord verwenden, z. B. MetaWhere oder Squeel. In meinem Fall nicht nützlich

Ich verwende einen von pg_search generierten Bereich, der etwas mehr als nur auswählen kann. Er enthält die Reihenfolge von ASC, wodurch eine saubere Union durcheinander gebracht wird. Ich möchte es mit einem handgefertigten Bereich 'oder' machen, der Dinge tut, die ich in pg_search nicht tun kann. Also musste ich es so machen.

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

Dh die Bereiche in SQL umwandeln, Klammern um jeden setzen, sie zusammenfügen und dann find_by_sql mit dem generierten SQL finden. Es ist ein bisschen Müll, aber es funktioniert.

Nein, sag mir nicht, dass ich "gegen: [: name ,: code]" in pg_search verwenden kann. Ich würde es gerne so machen, aber das Feld 'name' ist ein Hstore, den pg_search nicht verarbeiten kann noch. Daher muss der Gültigkeitsbereich nach Namen handgefertigt und dann mit dem Gültigkeitsbereich pg_search verknüpft werden.

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