Jak łączyć zapytania o zakres z OR zamiast AND?

Odpowiedzi:

107

Ty byś zrobił

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

W tej chwili nie ma żadnego innego wsparcia OR przez nową składnię AR3 (to znaczy bez użycia klejnotów innych firm).

Petros
źródło
21
i ze względów bezpieczeństwa warto to wyrazić jako Person.where ("name =? OR lastname =?", 'John', 'Smith')
CambridgeMike
6
Nadal ma to zastosowanie w Railsach 4? Nie ma nic lepszego w Rails 4?
Donato
11
Brak zmian w Rails 4, ale Rails 5 będą miały .orwbudowane .
wapno
3
to nie są zakresy, są to tylko 2 klauzule, lub bardzo konkretny przypadek zakresu. A co jeśli chciałbym łączyć w łańcuchy bardziej złożone zakresy, na przykład z połączeniami.
miguelfg
2
W Railsach 5 możesz także zrobić coś takiego jak Person.where ("name = 'John'"). Lub (Person.where ("lastname = 'Smith'"))
shyam
59

Zgodnie z tym żądaniem pull , Rails 5 obsługuje teraz następującą składnię dla zapytań łańcuchowych:

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

Istnieje również przeniesienie funkcjonalności do Rails 4.2 za pośrednictwem tego klejnotu.

Ryan Leaf
źródło
48

Użyj ARel

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)
Dan McNevin
źródło
1
Czy ktoś może wypowiedzieć się na temat tego, czy tego typu zapytania psują się w Postgres?
Olivier Lacan
ARel ma być agnostykiem DB.
Simon Perepelitsa
Chociaż wygląda to na dobry pomysł, ARel nie jest publicznym interfejsem API i używanie go może spowodować nieoczekiwane uszkodzenie aplikacji, więc odradzam to, chyba że zwrócisz szczególną uwagę na ewolucję interfejsu ARel API.
Olivier Lacan
7
@OlivierLacan Arel to publiczny interfejs API, a dokładniej, udostępnia go. Active Record zapewnia tylko wygodne metody, które używają go pod maską, jest całkowicie ważne, aby używać go samodzielnie. Jest zgodny z wersjami semantycznymi, więc jeśli nie zmieniasz wersji głównych (3.xx => 4.xx), nie musisz martwić się o zerwanie zmian.
Gray,
41

Wystarczy opublikować składnię Array dla tych samych zapytań w kolumnie LUB, aby pomóc im się zorientować.

Person.where(name: ["John", "Steve"])
daino3
źródło
2
Czek pytanie przed nazwą John Smith i przeciwko Nazwisko
steven_noble
11
@steven_noble - dlatego pogrubiłem „ta sama kolumna” z zapytaniem lub. Mogę całkowicie usunąć komentarz, ale pomyślałem, że będzie to pomocne dla osób przeglądających "lub" pytających SO.
daino3
38

Aktualizacja dla Rails4

nie wymaga żadnych klejnotów stron trzecich

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

Stara odpowiedź

nie wymaga żadnych klejnotów stron trzecich

Person.where(
    Person.where(:name => "John").where(:lastname => "Smith")
      .where_values.reduce(:or)
)
kissrobber
źródło
5
to naprawdę jedyna czysta odpowiedź na pytanie PO dotyczące zasobów i metody. pozostałe odpowiedzi albo (a) wymagają interfejsu API innej firmy, albo (b) wymagają interpolacji orlub innych operatorów w bloku tekstowym, z których żaden nie jest odzwierciedlony w kodzie OP.
allenwlee
6
tutaj jest przyzwoity artykuł wyjaśniający to coderwall.com/p/dgv7ag/ ...
allenwlee
4
Używany ...where_values.join(' OR ')w moim przypadku
Sash
2
Możesz pominąć .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }część, jeśli twoje zakresy nie mają żadnych zmiennych, które wymagają powiązania.
fatuhoku
22

Możesz także użyć MetaWhere gem, aby nie mylić kodu z elementami SQL:

Person.where((:name => "John") | (:lastname => "Smith"))
Simon Perepelitsa
źródło
3
+1. Następca MetaWhere jest squeel przez tego samego autora.
Thilo
22

Jeśli ktoś szuka zaktualizowanej odpowiedzi na to, wygląda na to, że istnieje żądanie ściągnięcia, aby pobrać to do Rails: https://github.com/rails/rails/pull/9052 .

Dzięki małpiej łatce @ j-mcnally dla ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) możesz wykonać następujące czynności:

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

Jeszcze cenniejsza jest możliwość łączenia lunet z 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) }

Następnie możesz znaleźć wszystkie osoby z imieniem lub nazwiskiem lub których rodzic ma nazwisko

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

Nie jest to najlepszy przykład użycia tego, ale po prostu próba dopasowania go do pytania.

codenamev
źródło
2
Której wersji lub Railsów potrzebujesz do tego?
Sash
3
Najnowsze dyskusje i prośby o ściągnięcie, które wprowadzą to do rdzenia Railsów, można znaleźć tutaj: github.com/rails/rails/pull/16052 Rails 5.0 jest przeznaczony do oficjalnego wydania .orfunkcjonalności łańcucha. Udało mi się użyć małpy łatki, o której wspomniałem na Railsach> = 3.2; jednakże możesz chcieć poczekać, aż Rails 5 wprowadzi jakiekolwiek długotrwałe zmiany do twojego kodu.
codenamev
1
Tak, został scalony i wydany z Railsami 5.
ameba
10

U mnie (Rails 4.2.5) działa tylko tak:

{ where("name = ? or name = ?", a, b) }
Zsolt
źródło
8

Byłby to dobry kandydat na MetaWhere, jeśli używasz Rails 3.0+, ale nie działa na Railsach 3.1. Zamiast tego możesz wypróbować squeel . Jest zrobiony przez tego samego autora. Oto, jak wykonałbyś łańcuch oparty na LUB:

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

Możesz łączyć i dopasowywać ORAZ / LUB, pośród wielu innych niesamowitych rzeczy .

dhulihan
źródło
8

Zaktualizowana wersja Rails / ActiveRecord może obsługiwać tę składnię natywnie. Wyglądałoby to podobnie do:

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

Jak wspomniano w tym żądaniu ściągnięcia https://github.com/rails/rails/pull/9052

Na razie po prostu trzymanie się następujących działa świetnie:

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Christian Fazzini
źródło
5
Nieobsługiwane w Railsach 4.2.5
fatuhoku
1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')nie działa. Powinien to być Foo.where (foo: 'bar1'). Or.where (Foo.where (bar: 'bar2'))
Ana María Martínez Gómez
6

Szyny 4 + luneta + 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

Próbowałem łączyć nazwane zakresy z .or i bez powodzenia, ale to działało w przypadku znalezienia czegokolwiek z ustawionymi parametrami logicznymi. Generuje SQL jak

SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
genkilabs
źródło
4

Szyny 4

scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
Abs
źródło
4

Jeśli chcesz podać zakres (zamiast pracować jawnie na całym zbiorze danych), oto co powinieneś zrobić z Railsami 5:

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

Lub:

def self.john_or_smith
  where(name: "John").or(where(lastname: "Smith"))
end
to mój projekt
źródło
To jest poprawne, ale bardziej dosłowna odpowiedź niż to samo, co technicznie robi stackoverflow.com/a/42894753/1032882 .
RudyOnRails
3

Jeśli nie możesz ręcznie napisać klauzuli where zawierającej instrukcję „lub” (tj. Chcesz połączyć dwa zakresy), możesz użyć union:

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

(źródło: ActiveRecord Query Union )

To zwróci wszystkie rekordy pasujące do dowolnego zapytania. Jednak zwraca to tablicę, a nie arel. Jeśli naprawdę chcesz zwrócić arel, przejdź do tego streszczenia: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

To wystarczy, o ile nie masz nic przeciwko łataniu szyn małp.

HashFail
źródło
2

Zobacz także te powiązane pytania: tutaj , tutaj i tutaj

Dla szyn 4, na podstawie tego artykułu i tej oryginalnej odpowiedzi

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

Zwróć uwagę na uwagę artykułu na końcu:

Rails 4.1+

Railsy 4.1 traktują default_scope tak samo jak zwykły zasięg. Domyślny zakres (jeśli taki posiadasz) jest zawarty w wyniku where_values, a inject (: or) doda instrukcję lub między domyślnym zakresem a twoim gdzie. To źle.

Aby to rozwiązać, wystarczy usunąć zakres zapytania.

HeyZiko
źródło
1

To bardzo wygodny sposób, który działa dobrze w Railsach 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
źródło
0

squeelgem zapewnia niezwykle łatwą drogę do osiągnięcia tego celu (przed tym Kiedyś coś takiego sposobu @ coloradoblue'S):

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
boulder_ruby
źródło
0

Tak więc odpowiedź na pierwotne pytanie, czy możesz połączyć zakresy za pomocą 'lub' zamiast 'i' wydaje się brzmieć „nie, nie możesz”. Możesz jednak ręcznie zakodować zupełnie inny zakres lub zapytanie, które wykona zadanie, lub użyć innego frameworka niż ActiveRecord, np. MetaWhere lub Squeel. Nie przydatne w moim przypadku

Ja 'or'ing zakres wygenerowany przez pg_search, który robi trochę więcej niż select, zawiera porządek przez ASC, co powoduje bałagan w czystej unii. Chcę 'lub' z ręcznie wykonanym teleskopem, który robi rzeczy, których nie mogę zrobić w pg_search. Więc musiałem to zrobić w ten sposób.

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

To znaczy zamień zakresy na sql, umieść nawiasy wokół każdego z nich, połącz je razem, a następnie znajdź_by_sql za pomocą wygenerowanego sql. To trochę bzdury, ale działa.

Nie, nie mów mi, że mogę użyć „against: [: name,: code]” w pg_search, chciałbym to zrobić w ten sposób, ale pole „name” to hstore, którego pg_search nie może obsłużyć jeszcze. Dlatego zakres według nazwy musi zostać utworzony ręcznie, a następnie połączony z zakresem pg_search.

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