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


137

Używam Rails3, ActiveRecord

Zastanawiam się tylko, jak połączyć zakresy za pomocą instrukcji OR zamiast AND.

na przykład

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

To zwykle zwraca:

name = 'John' AND lastname = 'Smith'

ale chciałbym:

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

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


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.


48

Użyj ARel

t = Person.arel_table

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

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

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

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

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.


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

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 .


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

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

4

Szyny 4

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

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


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.


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

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

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.


-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.