Rozważ proste skojarzenie ...
class Person
has_many :friends
end
class Friend
belongs_to :person
end
Jaki jest najczystszy sposób na zdobycie wszystkich osób, które NIE mają przyjaciół w ARel i / lub meta_where?
A co z has_many: poprzez wersję
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
end
class Friend
has_many :contacts
has_many :people, :through => :contacts, :uniq => true
end
class Contact
belongs_to :friend
belongs_to :person
end
Naprawdę nie chcę używać counter_cache - i z tego, co przeczytałem, nie działa z has_many: przez
Nie chcę pobierać wszystkich rekordów person.friends i przeglądać je w Rubim - chcę mieć zapytanie / zakres, którego mogę użyć z gemem meta_search
Nie przeszkadza mi koszt wydajności zapytań
Im dalej od rzeczywistego SQL, tym lepiej ...
ruby-on-rails
arel
meta-where
craic.com
źródło
źródło
DISTINCT
. W przeciwnym razie myślę, że w takim przypadku chciałbyś znormalizować dane i indeks. Mogę to zrobić, tworzącfriend_ids
hstore lub zserializowaną kolumnę. Wtedy możesz powiedziećPerson.where(friend_ids: nil)
not exists (select person_id from friends where person_id = person.id)
(A możepeople.id
lubpersons.id
, w zależności od tego, jaki jest twój stół). Nie jestem pewien, jaka jest najszybsza w konkretnej sytuacji, ale w przeszłości działało to dobrze, gdy ja nie próbował użyć ActiveRecord.Lepszy:
Hmt to w zasadzie to samo, polegasz na tym, że osoba bez przyjaciół również nie będzie miała kontaktów:
Aktualizacja
Mam pytanie
has_one
w komentarzach, więc po prostu aktualizuję. Sztuczka polega na tym, żeincludes()
oczekuje nazwy skojarzenia, alewhere
oczekuje nazwy tabeli. W przypadku ahas_one
skojarzenie będzie zazwyczaj wyrażane w liczbie pojedynczej, więc zmienia się, alewhere()
część pozostaje taka, jaka jest. Więc jeśliPerson
tylkohas_one :contact
wtedy twoje oświadczenie byłoby:Zaktualizuj 2
Ktoś zapytał o odwrotność, przyjaciół bez ludzi. Jak skomentowałem poniżej, to faktycznie uświadomiło mi, że ostatnie pole (powyżej: the
:person_id
) tak naprawdę nie musi być związane z modelem, który zwracasz, po prostu musi to być pole w tabeli łączenia. Wszyscy będą,nil
więc może to być każdy z nich. Prowadzi to do prostszego rozwiązania powyższego:A potem przełączenie tego, aby zwrócić przyjaciół bez ludzi, staje się jeszcze prostsze, zmieniasz tylko klasę z przodu:
Aktualizacja 3 - Rails 5
Dzięki @Anson za doskonałe rozwiązanie Rails 5 (daj mu kilka + 1-ek za jego odpowiedź poniżej), możesz użyć,
left_outer_joins
aby uniknąć ładowania skojarzenia:Umieściłem go tutaj, aby ludzie go mogli znaleźć, ale zasługuje na +1 za to. Świetny dodatek!
Aktualizacja 4 - Rails 6.1.0
Podziękowania dla Tima Park za wskazanie, że w nadchodzącym 6.1 możesz to zrobić:
Dzięki postowi, do którego też się podlinkował
źródło
has_one
stowarzyszenia, musisz zmienić nazwę stowarzyszenia wincludes
wezwaniu. Zakładając, że jest whas_one :contact
środku,Person
twój kod będziePerson.includes(:contact).where( :contacts => { :person_id => nil } )
self.table_name = "custom_friends_table_name"
), użyjPerson.includes(:friends).where(:custom_friends_table_name => {:id => nil})
.missing
metodę dokładnie zrobić to !Smathy ma dobrą odpowiedź Rails 3.
W przypadku Rails 5 możesz użyć,
left_outer_joins
aby uniknąć ładowania skojarzenia.Zapoznaj się z dokumentacją API . Został wprowadzony w żądaniu ściągnięcia nr 12071 .
źródło
.includes
dodatkowy koszt czasu ładowania nie byłby czymś , o co martwiłbym się zbytnio optymalizacją. Twój przypadek użycia może być inny.Person.joins('LEFT JOIN contacts ON contacts.person_id = persons.id').where('contacts.id IS NULL')
Działa również dobrze jako zakres. Robię to cały czas w moich projektach Railsowych.includes
, wszystkie te obiekty AR są ładowane do pamięci, co może być złe, gdy tabele stają się coraz większe. Jeśli nie potrzebujesz dostępu do rekordu kontaktu,left_outer_joins
nie ładuje kontaktu do pamięci. Szybkość żądania SQL jest taka sama, ale ogólna korzyść z aplikacji jest znacznie większa.Person.where(contacts: nil)
lubPerson.with(contact: contact)
gdyby użyć tego, gdzie wkracza zbyt daleko w `` właściwość '' - ale biorąc pod uwagę ten kontakt: jest już analizowany i identyfikowany jako skojarzenie, wydaje się logiczne, że mogliby łatwo ustalić, co jest wymagane ...Osoby, które nie mają przyjaciół
Albo co najmniej jednego przyjaciela
Możesz to zrobić z Arel, ustawiając zakresy na
Friend
A następnie Osoby, które mają co najmniej jednego znajomego:
Bez przyjaciół:
źródło
DEPRECATION WARNING: It looks like you are eager loading table(s)
Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string
Odpowiedzi z dmarkow i Unixmonkey dają mi to, czego potrzebuję - dziękuję!
Wypróbowałem oba w mojej prawdziwej aplikacji i uzyskałem dla nich czasy - oto dwa zakresy:
Uruchomiłem to z prawdziwą aplikacją - małym stolikiem z ~ 700 rekordami „osób” - średnio 5 przebiegów
Podejście Unixmonkey (
:without_friends_v1
) 813ms / querypodejście dmarkow (
:without_friends_v2
) 891ms / zapytanie (~ 10% wolniej)Ale wtedy przyszło mi do głowy, że nie potrzebuję telefonu do
DISTINCT()...
którego szukamPerson
płyt z NIEContacts
- więc wystarczy, że sąNOT IN
listą kontaktówperson_ids
. Więc wypróbowałem ten zakres:Daje to ten sam wynik, ale ze średnią 425 ms / połączenie - prawie połowę czasu ...
Teraz możesz potrzebować
DISTINCT
w innych podobnych zapytaniach - ale w moim przypadku wydaje się to działać dobrze.Dzięki za pomoc
źródło
Niestety, prawdopodobnie szukasz rozwiązania wykorzystującego SQL, ale możesz ustawić je w zakresie, a następnie po prostu użyć tego zakresu:
Następnie, aby je zdobyć, możesz po prostu zrobić
Person.without_friends
, a także możesz połączyć to z innymi metodami Arela:Person.without_friends.order("name").limit(10)
źródło
Podzapytanie skorelowane NIE ISTNIEJE powinno być szybkie, zwłaszcza gdy wzrasta liczba wierszy i stosunek rekordów potomnych do nadrzędnych.
źródło
Ponadto, aby odfiltrować na przykład według jednego znajomego:
źródło