Frage

Betrachten Sie eine einfache Assoziation ...

class Person
   has_many :friends
end

class Friend
   belongs_to :person
end

Was ist der sauberste Weg, um alle Personen zu bekommen, die keine Freunde in Arel und/oder Meta_where haben?

Und was ist dann mit einem Has_Many: durch 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

Ich möchte wirklich nicht counter_cache verwenden - und ich von dem, was ich gelesen habe, funktioniert es nicht mit Has_Many: durch

Ich möchte nicht alle Person mit Freunden ziehen und in Ruby durch sie durchgehen - ich möchte eine Abfrage/einen Umfang haben, den ich mit dem Edelstein für meta_search verwenden kann

Es macht mir nichts aus, die Leistungskosten der Fragen

Und je weiter von der tatsächlichen SQL entfernt ist, desto besser ...

War es hilfreich?

Lösung

Dies ist SQL immer noch ziemlich nahe, aber es sollte alle ohne Freunde im ersten Fall bringen:

Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)')

Andere Tipps

Besser:

Person.includes(:friends).where( :friends => { :person_id => nil } )

Für die HMT ist es im Grunde dasselbe, Sie verlassen sich darauf, dass eine Person ohne Freunde auch keine Kontakte hat:

Person.includes(:contacts).where( :contacts => { :person_id => nil } )

Aktualisieren

Habe eine Frage zu has_one In den Kommentaren, also nur aktualisiert. Der Trick hier ist das includes() erwartet den Namen des Vereins, aber der where erwartet den Namen der Tabelle. Für ein has_one Die Vereinigung wird im Allgemeinen im Singular ausgedrückt, so dass sich verändert, aber der where() Teil bleibt wie es ist. Also wenn a Person nur has_one :contact Dann wäre Ihre Aussage:

Person.includes(:contact).where( :contacts => { :person_id => nil } )

Update 2

Jemand fragte nach dem Inversen, Freunde ohne Menschen. Wie ich unten kommentierte, merkte mich das tatsächlich, dass das letzte Feld (oben: das: das :person_id) muss eigentlich nicht mit dem Modell verwandt sein, das Sie zurückkehren, es muss nur ein Feld in der Join -Tabelle sein. Sie werden alle sein nil So kann es jeder von ihnen sein. Dies führt zu einer einfacheren Lösung für die oben genannte:

Person.includes(:contacts).where( :contacts => { :id => nil } )

Und dann wechseln Sie dies, um die Freunde ohne Menschen zurückzugeben, noch einfacher. Sie ändern nur die Klasse an der Vorderseite:

Friend.includes(:contacts).where( :contacts => { :id => nil } )

Update 3 - Rails 5

Vielen Dank an @anson für die exzellente Rails 5 -Lösung (gib ihm einige +1 für seine Antwort unten), können Sie verwenden left_outer_joins Um das Laden der Assoziation zu vermeiden:

Person.left_outer_joins(:contacts).where( contacts: { id: nil } )

Ich habe es hier aufgenommen, damit die Leute es finden werden, aber er verdient die +1 dafür. Tolle Ergänzung!

Smathy hat eine gute Antwort 3 Antwort.

Für Schienen 5, Sie können verwenden left_outer_joins Um die Assoziation zu beladen.

Person.left_outer_joins(:contacts).where( contacts: { id: nil } )

Probier das aus API -Dokumente. Es wurde in Pull Anfrage eingeführt #12071.

Personen, die keine Freunde haben

Person.includes(:friends).where("friends.person_id IS NULL")

Oder das hat mindestens einen Freund

Person.includes(:friends).where("friends.person_id IS NOT NULL")

Sie können dies mit Arel tun, indem Sie Bereiche einrichten 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

Und dann Personen, die mindestens einen Freund haben:

Person.includes(:friends).merge(Friend.to_somebody)

Der Freundlosen:

Person.includes(:friends).merge(Friend.to_nobody)

Sowohl die Antworten von Dmarkow als auch Unixmonkey bekommen mir was ich brauche - danke!

Ich habe beide in meiner echten App ausprobiert und habe Zeit für sie - hier sind die beiden Bereiche:

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

Führte dies mit einer echten App - kleiner Tisch mit ~ 700 'Person' Records - Durchschnitt von 5 Läufen

Unixmonkeys Ansatz (:without_friends_v1) 813 ms / Abfrage

Dmarkows Ansatz (:without_friends_v2) 891 ms / Abfrage (~ 10% langsamer)

Aber dann fiel mir ein, dass ich den Anruf nicht brauche DISTINCT()... Ich suche Person Aufzeichnungen mit Nr Contacts - Also müssen sie es einfach sein NOT IN die Liste des Kontakts person_ids. Also habe ich diesen Bereich ausprobiert:

  scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }

Das bekommt das gleiche Ergebnis, aber mit durchschnittlich 425 ms/Anruf - fast die Hälfte der Zeit ...

Jetzt brauchst du vielleicht die DISTINCT In anderen ähnlichen Fragen scheint dies gut zu funktionieren.

Danke für Ihre Hilfe

Leider betrachten Sie wahrscheinlich eine Lösung mit SQL, aber Sie könnten sie in einen Bereich einstellen und dann nur diesen Umfang verwenden:

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

Dann, um sie zu bekommen, können Sie einfach tun Person.without_friends, und Sie können dies auch mit anderen Arel -Methoden ketten: Person.without_friends.order("name").limit(10)

Eine nicht existierende korrelierte Unterabfrage sollte schnell sein, insbesondere wenn die Anzahl der Zeilen und das Verhältnis von Kind zu übergeordneten Aufzeichnungen zunimmt.

scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)")

Auch zum Beispiel von einem Freund herausfiltern:

Friend.where.not(id: other_friend.friends.pluck(:id))
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top