Chcesz znaleźć rekordy bez powiązanych rekordów w Rails 3
Rozważmy proste skojarzenie...
class Person
has_many :friends
end
class Friend
belongs_to :person
end
Jaki jest najczystszy sposób, aby wszystkie osoby, które nie mają przyjaciół w ARel i / lub meta_where?
A potem co z has_many: through version
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 czytałem to nie działa z has_many: through
Nie chcę ciągnąć całej osoby.znajomi nagrywają i przeglądają je w Ruby - chcę mieć zapytanie / zakres, którego mogę użyć z meta_search gemNie przeszkadza mi koszt wykonania zapytań
I im dalej od rzeczywistego SQL tym lepiej...
8 answers
To jest nadal dość blisko SQL, ale powinien dostać każdy bez przyjaciół w pierwszym przypadku:
Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)')
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-16 15:22:42
Lepiej:
Person.includes(:friends).where( :friends => { :person_id => nil } )
Dla hmt to w zasadzie to samo, polegasz na tym, że osoba bez przyjaciół również nie będzie miała kontaktów: {]}
Person.includes(:contacts).where( :contacts => { :person_id => nil } )
Update
Mam pytanie o has_one
w komentarzach, więc po prostu aktualizuję. Sztuczka polega na tym, że includes()
oczekuje nazwy asocjacji, ale where
oczekuje nazwy tabeli. Dla has_one
Asocjacja będzie ogólnie wyrażona w liczbie pojedynczej, tak że się zmienia, ale where()
część pozostaje taka, jaka jest. Więc jeśli Person
tylko has_one :contact
to Twoje stwierdzenie będzie:
Person.includes(:contact).where( :contacts => { :person_id => nil } )
Update 2
Ktoś pytał o odwrotność, przyjaciele bez ludzi. Jak komentowałem poniżej, uświadomiło mi to, że ostatnie pole (powyżej: :person_id
) nie musi być związane z Modelem, który zwracasz, tylko musi być polem w tabeli join. Wszystkie będą nil
, więc może to być każdy z nich. Prowadzi to do prostszego rozwiązania powyższego:
Person.includes(:contacts).where( :contacts => { :id => nil } )
I wtedy zmiana tego na powrót przyjaciół bez ludzi staje się jeszcze prostsza, zmieniasz tylko klasę z przodu: {]}
Friend.includes(:contacts).where( :contacts => { :id => nil } )
Update 3-Rails 5
Dzięki @Anson za doskonałe rozwiązanie Rails 5 (daj mu kilka + 1 za jego odpowiedź poniżej), możesz użyć left_outer_joins
, aby uniknąć ładowania skojarzenia:
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
Umieściłem go tutaj, aby ludzie go znaleźli, ale zasługuje na +1 za to. Świetny dodatek!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-07 00:54:24
Smathy ma dobrą odpowiedź Rails 3.
Dla Rails 5, możesz użyć left_outer_joins
, aby uniknąć wczytywania asocjacji.
Person.left_outer_joins(:contacts).where( contacts: { id: nil } )
Sprawdź dokumenty api. Został wprowadzony w pull request #12071.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-09 16:19:01
Osoby, które nie mają znajomych
Person.includes(:friends).where("friends.person_id IS NULL")
Lub które mają przynajmniej jednego przyjaciela
Person.includes(:friends).where("friends.person_id IS NOT NULL")
Możesz to zrobić za pomocą Arel, ustawiając zakresy na Friend
class Friend
belongs_to :person
scope :to_somebody, ->{ where arel_table[:person_id].not_eq(nil) }
scope :to_nobody, ->{ where arel_table[:person_id].eq(nil) }
end
A potem osoby, które mają przynajmniej jednego przyjaciela:
Person.includes(:friends).merge(Friend.to_somebody)
Bez przyjaciół:
Person.includes(:friends).merge(Friend.to_nobody)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-29 16:05:09
Obie odpowiedzi od dmarkow i Unixmonkey dają mi to , czego potrzebuję-Dziękuję!
Wypróbowałem oba w mojej prawdziwej aplikacji i mam dla nich timingi - oto dwa zakresy:
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
scope :without_friends_v1, -> { where("(select count(*) from contacts where person_id=people.id) = 0") }
scope :without_friends_v2, -> { where("id NOT IN (SELECT DISTINCT(person_id) FROM contacts)") }
end
W 2011 roku firma została założona przez firmę Garmin Sp. z o. o. z siedzibą w Warszawie, która od 2011 roku zajmuje się produkcją i dystrybucją sprzętu komputerowego.]}
Unixmonkey ' s approach (:without_friends_v1
) 813ms / query
Dmarkow ' s approach (:without_friends_v2
) 891ms / query (~ 10% wolniej)
Ale wtedy przyszło mi do głowy, że nie potrzebuję telefonu do DISTINCT()...
jestem Szukam Person
rekordów bez Contacts
- więc muszą być NOT IN
lista kontaktów person_ids
. Więc wypróbowałem ten zakres:
scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }
To daje 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-11 19:02:30
Niestety, prawdopodobnie patrzysz na rozwiązanie obejmujące SQL, ale możesz ustawić je w zakresie, a następnie po prostu użyć tego zakresu:
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
scope :without_friends, where("(select count(*) from contacts where person_id=people.id) = 0")
end
Następnie, aby je uzyskać, możesz po prostu zrobić Person.without_friends
, i możesz również połączyć to z innymi metodami Arel: Person.without_friends.order("name").limit(10)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-16 00:29:54
A nie istnieje skorelowane zapytania podrzędne powinny być szybkie, zwłaszcza gdy liczba wierszy i stosunek rekordów potomnych do rekordów nadrzędnych wzrasta.
scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)")
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-16 08:40:28
Również, aby odfiltrować przez jednego znajomego na przykład:
Friend.where.not(id: other_friend.friends.pluck(:id))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-01 23:53:13