web-dev-qa-db-fra.com

Rails: include vs.: jointures

C'est plutôt une question "Pourquoi les choses fonctionnent-elles de cette façon" plutôt qu'une question "Je ne sais pas comment faire cela" ...

Donc, l'évangile sur l'extraction des enregistrements associés que vous savez utiliser va utiliser :include parce que vous obtiendrez une jointure et éviterez toute une série de requêtes supplémentaires:

Post.all(:include => :comments)

Cependant, lorsque vous consultez les journaux, il n'y a pas de jointure:

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) 

Cela est prend un raccourci parce que tous les commentaires sont extraits en même temps, mais ce n’est pas encore une jointure (c’est ce que toute la documentation semble indiquer). Le seul moyen d'obtenir une jointure est d'utiliser :joins au lieu de :include:

Post.all(:joins => :comments)

Et les journaux montrent:

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

Est-ce que je manque quelque chose? J'ai une application avec une demi-douzaine d'associations et sur un écran, j'affiche les données de chacune d'entre elles. On dirait qu'il serait préférable d'avoir une requête jointe au lieu de 6 individus. Je sais que, du point de vue des performances, il n'est pas toujours préférable de faire une jointure plutôt que des requêtes individuelles (en fait, si vous prenez du temps, il semblerait que les deux requêtes individuelles ci-dessus soient plus rapides que la jointure), mais après tous les documents Je suis en train de lire Je suis surpris de voir que :include ne fonctionne pas comme annoncé.

Peut-être que Rails est conscient du problème de performances et ne participe pas, sauf dans certains cas?

338
Rob Cameron

Il semble que la fonctionnalité :include ait été modifiée avec Rails 2.1. Rails effectuait la jointure dans tous les cas, mais pour des raisons de performances, il a été modifié pour utiliser plusieurs requêtes dans certaines circonstances. Cet article de blog par Fabio Akita a quelques bonnes informations sur le changement (voir la section intitulée "Chargement optimisé").

175
Greg Campbell

.joins joindra simplement les tables et apportera les champs sélectionnés en retour. si vous appelez des associations sur le résultat de la requête de jointure, les requêtes de base de données seront à nouveau déclenchées.

:includes chargera avec impatience les associations incluses et les ajoutera en mémoire. :includes charge tous les attributs des tables incluses. Si vous appelez des associations sur le résultat de la requête include, aucune requête ne sera déclenchée.

88
Prem

La différence entre les jointures et les inclusion est que l'utilisation de l'instruction include génère une requête SQL beaucoup plus volumineuse, chargeant en mémoire tous les attributs des autres tables.

Par exemple, si vous avez une table pleine de commentaires et que vous utilisez a: join => users pour extraire toutes les informations utilisateur à des fins de tri, etc. cela fonctionnera correctement et prendra moins de temps que: include, mais vous voulez afficher le commentaire ainsi que le nom de l'utilisateur, son email, etc. Pour obtenir les informations à l'aide de: join, il devra effectuer des requêtes SQL distinctes pour chaque utilisateur extrait, tandis que si vous avez utilisé: include, cette information est prête à être utilisée.

Excellent exemple:

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

70
holden

Outre les considérations de performances, il existe également une différence fonctionnelle. Lorsque vous joignez des commentaires, vous demandez des publications contenant des commentaires - une jointure interne par défaut. Lorsque vous incluez des commentaires, vous demandez toutes les publications - une jointure externe.

53
Brian Maltzan

Je lisais récemment plus sur la différence entre :joins et :includes dans Rails. Voici une explication de ce que j'ai compris (avec des exemples :))

Considérez ce scénario:

  • Un utilisateur a_de nombreux commentaires et un commentaire appartient à un utilisateur.

  • Le modèle utilisateur possède les attributs suivants: Nom (chaîne), Age (entier). Le modèle de commentaire possède les attributs suivants: Contenu, id_utilisateur. Pour un commentaire, user_id peut être null.

rejoint:

: jointes effectue un jointure interne entre deux tables. Ainsi

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">]>

va chercher tous les enregistrements où user_id (de la table des commentaires) est égal à user.id (table des utilisateurs). Donc si vous le faites

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

#=> <ActiveRecord::Relation []>

Vous obtiendrez un tableau vide comme indiqué.

De plus, join ne charge pas la table jointe en mémoire. Donc si tu fais

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

Comme vous le voyez, comment_1.user.age lancera à nouveau une requête de base de données en arrière-plan pour obtenir les résultats.

inclut:

: includes effectue un jointure externe gauche entre les deux tables. Ainsi

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">]>

se traduira par ne table jointe avec tous les enregistrements de la table de commentaires. Donc, si vous faites

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">]>

il récupérera les enregistrements où comments.user_id est nil, comme indiqué.

De plus, include charge les deux tables en mémoire. Donc si tu fais

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Comme vous pouvez le constater, comment_1.user.age charge simplement le résultat de la mémoire sans déclencher une requête de base de données en arrière-plan.

50
Aaditi Jain

tl; dr

Je les contraste de deux manières:

jointures - Pour la sélection conditionnelle des enregistrements.

includes - Lors de l'utilisation d'une association sur chaque membre d'un jeu de résultats.

Version plus longue

Jointures est destiné à filtrer le jeu de résultats provenant de la base de données. Vous l'utilisez pour définir des opérations sur votre table. Pensez à cela comme une clause where qui exécute la théorie des ensembles.

Post.joins(:comments)

est le même que

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

Sauf que s'il y a plus d'un commentaire, vous recevrez des messages en double avec les jointures. Mais chaque article sera un article qui a des commentaires. Vous pouvez corriger cela avec distinct:

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

Dans le contrat, la méthode includes s'assurera simplement qu'il n'y a pas de requête de base de données supplémentaire lors du référencement de la relation (afin d'éviter les requêtes n + 1).

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

La morale est d'utiliser joins lorsque vous souhaitez effectuer des opérations sur un ensemble conditionnel et d'utiliser includes lorsque vous allez utiliser une relation sur chaque membre d'une collection.

9
Kevin Choubacha

.joins fonctionne comme une jointure de base de données et joint deux ou plusieurs tables et récupère les données sélectionnées depuis le backend (base de données).

.includes work as joint gauche de la base de données. Il a chargé tous les enregistrements du côté gauche, n'a pas d'importance du modèle du côté droit. Il est utilisé pour un chargement rapide car il charge tous les objets associés en mémoire. Si nous appelons des associations sur le résultat de la requête include, il ne lance pas de requête sur la base de données. Il renvoie simplement les données de la mémoire car il a déjà chargé des données en mémoire.

4
user3412661

'jointes' vient juste de joindre des tables et lorsque vous appelez des associations sur des jointures, il lancera à nouveau une requête (cela signifie que plusieurs requêtes seront déclenchées)

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)

le nombre total de SQL est 11 dans ce cas

Mais avec 'includes', les associations incluses seront chargées avec impatience et ajoutées en mémoire (charger toutes les associations lors du premier chargement) et ne plus lancer de requête

lorsque vous obtenez des enregistrements avec des éléments include tels que @ records = User.includes (: organisations) .where ("organisations.user_id = 1"), la requête sera

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} aucune requête ne sera déclenchée

0
Thorin