Rails: include vs.: Joins


345

Dies ist eher eine Frage "Warum funktionieren die Dinge so?" Als eine Frage "Ich weiß nicht, wie ich das machen soll" ...

Das Evangelium zum Abrufen zugehöriger Datensätze, von denen Sie wissen, dass Sie sie verwenden werden, lautet: Verwenden :includeSie sie, weil Sie einen Join erhalten und eine ganze Reihe zusätzlicher Abfragen vermeiden:

Post.all(:include => :comments)

Wenn Sie sich jedoch die Protokolle ansehen, findet keine Verknüpfung statt:

Post Load (3.7ms)   SELECT * FROM "posts"
Comment Load (0.2ms)   SELECT "comments.*" FROM "comments" 
                       WHERE ("comments".post_id IN (1,2,3,4)) 
                       ORDER BY created_at asc) 

Es wird eine Verknüpfung verwendet, da alle Kommentare auf einmal abgerufen werden, aber es handelt sich immer noch nicht um eine Verknüpfung (was in der gesamten Dokumentation zu sagen scheint). Die einzige Möglichkeit, einen Join zu erhalten, besteht darin, :joinsFolgendes zu verwenden :include:

Post.all(:joins => :comments)

Und die Protokolle zeigen:

Post Load (6.0ms)  SELECT "posts".* FROM "posts" 
                   INNER JOIN "comments" ON "posts".id = "comments".post_id

Vermisse ich etwas Ich habe eine App mit einem halben Dutzend Assoziationen und auf einem Bildschirm zeige ich Daten von allen an. Es scheint besser zu sein, eine verbundene Abfrage anstelle von 6 Personen zu haben. Ich weiß, dass es in Bezug auf die Leistung nicht immer besser ist, einen Join durchzuführen, als einzelne Abfragen (wenn Sie nach Zeitaufwand arbeiten, scheinen die beiden oben genannten einzelnen Abfragen schneller zu sein als der Join), aber schließlich alle Dokumente Ich habe gelesen, dass ich überrascht bin, dass es :includenicht wie angekündigt funktioniert.

Vielleicht Rails ist bewusst , die Performance - Problem und kommen nicht außer in bestimmten Fällen?


3
Wenn Sie eine ältere Version von Rails verwendet haben, geben Sie dies bitte über Tags oder in Ihrem Fragenkörper an. Andernfalls, wenn Sie Rails 4 NOW verwenden, ist es includes(für jeden, der dies liest)
onebree

Auch gibt es jetzt: Preload und: eifrige_Ladung blog.bigbinary.com/2013/07/01/…
CJW

Antworten:


179

Es scheint, dass die :includeFunktionalität mit Rails 2.1 geändert wurde. Rails wurde in allen Fällen für den Join verwendet, aus Leistungsgründen wurde er jedoch geändert, um unter bestimmten Umständen mehrere Abfragen zu verwenden. Dieser Blog-Beitrag von Fabio Akita enthält einige gute Informationen zur Änderung (siehe Abschnitt "Optimiertes eifriges Laden").



Das ist sehr hilfreich, danke. Ich wünschte jedoch, es gäbe eine Möglichkeit, Rails zu zwingen, den Join auch ohne ein "Wo" durchzuführen, das dies erfordert. In einigen Fällen wissen Sie, dass der Join effizienter ist und nicht das Risiko einer Duplizierung besteht.
Jonathan Swartz


@ JonathanSwartz Sieht aus wie neue Version Rails unterstützen dies mit Eifersucht . Vielen Dank für den Link NathanLong
Rubyprince

92

.joinswird nur die Tabellen verbinden und ausgewählte Felder zurückbringen. Wenn Sie Assoziationen für Joins-Abfrageergebnisse aufrufen, werden Datenbankabfragen erneut ausgelöst

:includeslädt eifrig die enthaltenen Assoziationen und fügt sie in den Speicher ein. :includesLädt alle enthaltenen Tabellenattribute. Wenn Sie Assoziationen für das Ergebnis der Include-Abfrage aufrufen, werden keine Abfragen ausgelöst


71

Der Unterschied zwischen Joins und Include besteht darin, dass die Verwendung der include-Anweisung eine viel größere SQL-Abfrage generiert, die alle Attribute aus den anderen Tabellen in den Speicher lädt.

Wenn Sie beispielsweise eine Tabelle voller Kommentare haben und a: joins => users verwenden, um alle Benutzerinformationen für Sortierzwecke usw. abzurufen, funktioniert dies einwandfrei und dauert weniger als: include, aber Sie möchten anzeigen Der Kommentar zusammen mit dem Benutzernamen, der E-Mail-Adresse usw. Um die Informationen mithilfe von: joins abzurufen, müssen separate SQL-Abfragen für jeden Benutzer durchgeführt werden, den sie abrufen. Wenn Sie Folgendes verwenden: Diese Informationen sind einsatzbereit.

Tolles Beispiel:

http://railscasts.com/episodes/181-include-vs-joins


55

Ich habe kürzlich mehr über den Unterschied zwischen :joinsund :includesin Schienen gelesen . Hier ist eine Erklärung dessen, was ich verstanden habe (mit Beispielen :))

Stellen Sie sich dieses Szenario vor:

  • Ein Benutzer hat viele Kommentare und ein Kommentar gehört einem Benutzer.

  • Das Benutzermodell weist die folgenden Attribute auf: Name (Zeichenfolge), Alter (Ganzzahl). Das Kommentarmodell weist die folgenden Attribute auf: Inhalt, Benutzer-ID. Für einen Kommentar kann eine Benutzer-ID null sein.

Tritt bei:

: joins führt einen inneren Join zwischen zwei Tabellen durch. Somit

Comment.joins(:user)

#=> <ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first   comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">, 
     #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,    
     #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">]>

wird holen , wo user_id (Kommentare Tabelle) alle Datensätze gleich user.id (Benutzer - Tabelle). Also wenn du es tust

Comment.joins(:user).where("comments.user_id is null")

#=> <ActiveRecord::Relation []>

Sie erhalten wie gezeigt ein leeres Array.

Außerdem lädt Joins die verknüpfte Tabelle nicht in den Speicher. Also wenn du es tust

comment_1 = Comment.joins(:user).first

comment_1.user.age
#=>←[1m←[36mUser Load (0.0ms)←[0m  ←[1mSELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1←[0m  [["id", 1]]
#=> 24

Wie Sie sehen, comment_1.user.agewird im Hintergrund erneut eine Datenbankabfrage ausgelöst, um die Ergebnisse zu erhalten

Beinhaltet:

: include führt eine linke äußere Verknüpfung zwischen den beiden Tabellen durch. Somit

Comment.includes(:user)

#=><ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">,
   #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,
   #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">,    
   #<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

führt zu einer verknüpften Tabelle mit allen Datensätzen aus der Kommentartabelle. Also wenn du es tust

Comment.includes(:user).where("comment.user_id is null")
#=> #<ActiveRecord::Relation [#<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

Es werden Datensätze abgerufen, bei denen comment.user_id wie gezeigt Null ist.

Außerdem werden beide Tabellen in den Speicher geladen. Also wenn du es tust

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Wie Sie sehen können, lädt comment_1.user.age einfach das Ergebnis aus dem Speicher, ohne im Hintergrund eine Datenbankabfrage auszulösen.


Ist das für Rails 4?
Onebree

@ HunterStevens: Ja, das ist es
Aaditi Jain

54

Neben Leistungsüberlegungen gibt es auch einen funktionalen Unterschied. Wenn Sie Kommentaren beitreten, fragen Sie nach Posts mit Kommentaren - standardmäßig einem inneren Join. Wenn Sie Kommentare einfügen, fragen Sie nach allen Posts - einem Outer Join.


10

tl; dr

Ich kontrastiere sie auf zwei Arten:

Joins - Zur bedingten Auswahl von Datensätzen.

Includes - Bei Verwendung einer Zuordnung für jedes Mitglied einer Ergebnismenge.

Längere Version

Joins soll die aus der Datenbank stammende Ergebnismenge filtern. Sie verwenden es, um Set-Operationen für Ihre Tabelle auszuführen. Stellen Sie sich dies als eine where-Klausel vor, die die Mengenlehre durchführt.

Post.joins(:comments)

ist das gleiche wie

Post.where('id in (select post_id from comments)')

Wenn jedoch mehr als ein Kommentar vorhanden ist, erhalten Sie doppelte Beiträge mit den Verknüpfungen zurück. Aber jeder Beitrag wird ein Beitrag sein, der Kommentare enthält. Sie können dies mit folgenden Punkten korrigieren:

Post.joins(:comments).count
=> 10
Post.joins(:comments).distinct.count
=> 2

Im Vertrag stellt die includesMethode einfach sicher, dass beim Verweisen auf die Beziehung keine zusätzlichen Datenbankabfragen vorhanden sind (damit keine n + 1-Abfragen durchgeführt werden).

Post.includes(:comments).count
=> 4 # includes posts without comments so the count might be higher.

Die Moral lautet: Verwenden joinsSie diese Option, wenn Sie bedingte Mengenoperationen ausführen möchten, und verwenden includesSie sie, wenn Sie für jedes Mitglied einer Sammlung eine Beziehung verwenden möchten .


Das distinctbringt mich jedes Mal. Vielen Dank!
Ben Hull

4

.joins fungiert als Datenbankverknüpfung und verbindet zwei oder mehr Tabellen und ruft ausgewählte Daten aus dem Backend (Datenbank) ab.

.inklusive Arbeit als linker Join der Datenbank. Es hat alle Datensätze der linken Seite geladen, hat keine Relevanz für das Modell der rechten Seite. Es wird zum eifrigen Laden verwendet, da alle zugehörigen Objekte in den Speicher geladen werden. Wenn wir Assoziationen für das Ergebnis der Include-Abfrage aufrufen, wird keine Abfrage für die Datenbank ausgelöst. Es werden einfach Daten aus dem Speicher zurückgegeben, da bereits Daten in den Speicher geladen wurden.


0

'joins' wird nur zum Verknüpfen von Tabellen verwendet. Wenn Sie Assoziationen für Joins aufgerufen haben, wird die Abfrage erneut ausgelöst (dies bedeutet, dass viele Abfragen ausgelöst werden).

lets suppose you have tow model, User and Organisation
User has_many organisations
suppose you have 10 organisation for a user 
@records= User.joins(:organisations).where("organisations.user_id = 1")
QUERY will be 
 select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1

it will return all records of organisation related to user
and @records.map{|u|u.organisation.name}
it run QUERY like 
select * from organisations where organisations.id = x then time(hwo many organisation you have)

Die Gesamtzahl von SQL beträgt in diesem Fall 11

Aber mit 'Includes' werden die enthaltenen Assoziationen eifrig geladen und in den Speicher eingefügt (alle Assoziationen beim ersten Laden laden) und die Abfrage nicht erneut ausgelöst

Wenn Sie Datensätze mit Includes wie @ records = User.includes (: organisations) .where ("organisations.user_id = 1") erhalten, wird die Abfrage ausgeführt

select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
and 


 select * from organisations where organisations.id IN(IDS of organisation(1, to 10)) if 10 organisation
and when you run this 

@ records.map {| u | u.organisation.name} Es wird keine Abfrage ausgelöst

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.