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 einer 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 counter_cache wirklich nicht verwenden - und nach dem, was ich gelesen habe, funktioniert es nicht mit has_many: through
Ich möchte nicht alle person.friends-Datensätze abrufen und sie in Ruby durchlaufen - ich möchte eine Abfrage / einen Bereich haben, den ich mit dem meta_search-Juwel verwenden kann
Die Leistungskosten der Abfragen stören mich nicht
Und je weiter von SQL entfernt, desto besser ...
ruby-on-rails
arel
meta-where
craic.com
quelle
quelle
DISTINCT
. Andernfalls sollten Sie in diesem Fall die Daten und den Index normalisieren. Ich könnte das tun, indem ich einenfriend_ids
hstore oder eine serialisierte Spalte erstelle. Dann könnte man sagenPerson.where(friend_ids: nil)
not exists (select person_id from friends where person_id = person.id)
(oder vielleichtpeople.id
oderpersons.id
, je nachdem, was Ihre Tabelle ist). Ich bin mir nicht sicher, was in einer bestimmten Situation am schnellsten ist, aber in der Vergangenheit hat dies für mich gut funktioniert, wenn ich hat nicht versucht, ActiveRecord zu verwenden.Besser:
Für das hmt ist es im Grunde dasselbe, Sie verlassen sich auf die Tatsache, dass eine Person ohne Freunde auch keine Kontakte hat:
Aktualisieren
Ich habe eine Frage zu
has_one
in den Kommentaren, also nur aktualisieren. Der Trick dabei ist,includes()
dass der Name der Zuordnungwhere
erwartet wird, aber der Name der Tabelle. Für a wirdhas_one
die Assoziation im Allgemeinen im Singular ausgedrückt, so dass sich dies ändert, aber derwhere()
Teil bleibt wie er ist. Wenn alsoPerson
nurhas_one :contact
dann Ihre Aussage wäre:Update 2
Jemand fragte nach dem Gegenteil, Freunde ohne Menschen. Wie ich unten kommentierte, wurde mir tatsächlich klar, dass das letzte Feld (oben: das
:person_id
) nicht unbedingt mit dem Modell verknüpft sein muss, das Sie zurückgeben, sondern nur ein Feld in der Verknüpfungstabelle. Sie werden allenil
so sein, dass es jeder von ihnen sein kann. Dies führt zu einer einfacheren Lösung für das oben Gesagte:Und dann wird es noch einfacher, dies zu ändern, um die Freunde ohne Personen zurückzugeben. Sie ändern nur die Klasse an der Vorderseite:
Update 3 - Schienen 5
Dank @Anson für die hervorragende Rails 5-Lösung (geben Sie ihm einige +1 für seine Antwort unten) können Sie das
left_outer_joins
Laden der Assoziation vermeiden:Ich habe es hier aufgenommen, damit die Leute es finden, aber er verdient die +1 dafür. Tolle Ergänzung!
Update 4 - Rails 6.1
Vielen Dank an Tim Park für den Hinweis, dass Sie dies in der kommenden Version 6.1 tun können:
Dank des Beitrags, auf den er auch verlinkt hat.
quelle
has_one
Zuordnung haben, müssen Sie den Namen der Zuordnung imincludes
Anruf ändern . Angenommen, es wärehas_one :contact
drin,Person
dann wäre Ihr CodePerson.includes(:contact).where( :contacts => { :person_id => nil } )
self.table_name = "custom_friends_table_name"
) einen benutzerdefinierten Tabellennamen verwenden , verwenden SiePerson.includes(:friends).where(:custom_friends_table_name => {:id => nil})
.missing
Methode hinzu, um genau dies zu tun !smathy hat eine gute Rails 3 Antwort.
Bei Rails 5 können Sie das
left_outer_joins
Laden der Zuordnung vermeiden.Schauen Sie sich die API-Dokumente an . Es wurde in der Pull-Anfrage Nr. 12071 eingeführt .
quelle
.includes
ich mir keine Sorgen über die Optimierung machen würde, wenn zusätzliche Kosten für die Ladezeit anfallen würden. Ihr Anwendungsfall kann anders sein.Person.joins('LEFT JOIN contacts ON contacts.person_id = persons.id').where('contacts.id IS NULL')
Es funktioniert auch als Zielfernrohr einwandfrei. Ich mache das die ganze Zeit in meinen Rails-Projekten.includes
, werden alle diese AR-Objekte in den Speicher geladen. Dies kann eine schlechte Sache sein, wenn Tabellen immer größer werden. Wenn Sie keinen Zugriff auf den Kontaktdatensatz benötigen, wird der Kontaktleft_outer_joins
nicht in den Speicher geladen. Die SQL-Anforderungsgeschwindigkeit ist gleich, aber der allgemeine App-Vorteil ist viel größer.Person.where(contacts: nil)
oderPerson.with(contact: contact)
wenn sie verwenden, wo zu weit in die „Properness“ eingegriffen wird - aber angesichts dieses Kontakts, der bereits analysiert und als Assoziation identifiziert wird, scheint es logisch, dass arel leicht herausfinden könnte, was erforderlich ist ...Personen, die keine Freunde haben
Oder die mindestens einen Freund haben
Sie können dies mit Arel tun, indem Sie Bereiche einrichten
Friend
Und dann Personen, die mindestens einen Freund haben:
Der Freundlose:
quelle
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
Sowohl die Antworten von dmarkow als auch von Unixmonkey bringen mir das, was ich brauche - Danke!
Ich habe beide in meiner realen App ausprobiert und Timings für sie erhalten - Hier sind die beiden Bereiche:
Lief dies mit einer echten App - kleiner Tabelle mit ~ 700 'Person'-Datensätzen - durchschnittlich 5 Läufe
Unixmonkeys Ansatz (
:without_friends_v1
) 813 ms / Abfragedmarkows Ansatz (
:without_friends_v2
) 891 ms / Abfrage (~ 10% langsamer)Aber dann kam mir der Gedanke, dass ich den Anruf nicht brauche, um nach Aufzeichnungen mit NEIN zu
DISTINCT()...
suchen - sie müssen also nur die Kontaktliste sein . Also habe ich diesen Bereich ausprobiert:Person
Contacts
NOT IN
person_ids
Das ergibt das gleiche Ergebnis, aber mit durchschnittlich 425 ms / Anruf - fast die Hälfte der Zeit ...
Jetzt brauchen Sie vielleicht die
DISTINCT
in anderen ähnlichen Abfragen - aber für meinen Fall scheint dies gut zu funktionieren.Danke für Ihre Hilfe
quelle
Leider suchen Sie wahrscheinlich nach einer Lösung mit SQL, aber Sie können sie in einem Bereich festlegen und dann einfach diesen Bereich verwenden:
Um sie zu erhalten, können Sie dies einfach tun
Person.without_friends
und dies auch mit anderen Arel-Methoden verketten:Person.without_friends.order("name").limit(10)
quelle
Eine NICHT EXISTIERTE korrelierte Unterabfrage sollte schnell sein, insbesondere wenn die Zeilenanzahl und das Verhältnis von untergeordneten zu übergeordneten Datensätzen zunehmen.
quelle
Zum Beispiel, um von einem Freund herauszufiltern:
quelle