Rails:inclure vs:joint
c'est plus une question de "pourquoi faire les choses de cette façon" qu'une question de "Je ne sais pas comment faire"...
donc le gospel en tirant des enregistrements associés que vous savez que vous allez utiliser est d'utiliser :include
parce que vous obtiendrez une jointure et éviter tout un tas de requêtes supplémentaires:
Post.all(:include => :comments)
cependant, quand vous regardez les logs, il n'y a pas de jointure se produire:
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)
Il est prend un raccourci parce qu'il tire tous les commentaires à la fois, mais ce n'est toujours pas une jointure (ce qui est ce que toute la documentation semble dire). La seule façon d'obtenir une jointure est d'utiliser :joins
au lieu de :include
:
Post.all(:joins => :comments)
Et les journaux:
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, je l'affichage des données de tous. Me semble qu'il serait mieux 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 tenez compte du temps passé, il semble que les deux requêtes individuelles ci-dessus sont plus rapides que la jointure), mais après tous les docs que j'ai lus, je suis surpris de voir :include
ne fonctionne pas comme annoncé.
peut-être Rails est conscient de la question de la performance et ne se joint pas sauf dans certains les cas?
8 réponses
il semble que la fonctionnalité :include
ait été changée avec les Rails 2.1. Les Rails étaient utilisés pour faire la jointure dans tous les cas, mais pour des raisons de performance il a été modifié pour utiliser des requêtes multiples dans certaines circonstances. ce billet de blog de Fabio Akita contient de bonnes informations sur le changement (voir la section intitulée"chargement optimisé et impatient").
.joins
va juste rejoindre les tables et apporte des champs sélectionnés en retour. si vous appelez des associations sur le résultat de la requête joins, il va lancer des requêtes de base de données à nouveau
:includes
chargera les associations incluses et les ajoutera en mémoire. :includes
charge tous les attributs de tableaux inclus. Si vous appelez des associations sur le résultat de requête include, il ne lancera pas de requêtes
Mon blog post a quelques explication détaillée des différences
la différence entre jointures et include est que l'utilisation de l'instruction include génère une requête SQL beaucoup plus grande chargeant dans la mémoire tous les attributs de l'autre table(s).
par exemple, si vous avez une table pleine de commentaires et que vous utilisez un :joins => utilisateurs pour extraire toutes les informations utilisateur à des fins de tri, etc cela fonctionnera très bien et prendra moins de temps que :include, Mais dites que vous voulez afficher le commentaire avec le nom de l'utilisateur, e-mail, etc. Obtenir l'information utilisant :joins, il devra faire des requêtes SQL séparées pour chaque utilisateur qu'il récupère, alors que si vous utilisez :include cette information est prête à l'emploi.
grand exemple:
en plus des considérations de performance, il y a aussi une différence fonctionnelle. Lorsque vous rejoignez comments, vous demandez pour les messages qui ont des commentaires - une adhésion interne par défaut. Lorsque vous incluez des commentaires, vous demandez tous les messages - une jointure externe.
j'ai récemment lu plus sur la différence entre :joins
et :includes
dans les rails. Voici une explication de ce que j'ai compris (avec des exemples :))
envisager ce scénario:
-
un utilisateur a un grand nombre de commentaires et un commentaire appartient à un utilisateur.
-
le modèle utilisateur possède les attributs suivants: Nom(chaîne), âge(entier). Le modèle Commentaire a la attributs suivants:Contenu, user_id. Pour un commentaire un user_id peut être null.
Rejoint:
: joins exécute un joint intérieur 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 récupérer tous les enregistrements où user_id (de la table de commentaires) est égal à user.id (users table). ainsi si vous faites
Comment.joins(:user).where("comments.user_id is null")
#=> <ActiveRecord::Relation []>
, Vous obtiendrez un tableau vide, comme illustré.
en outre les jointures ne charge pas la table jointe en mémoire. Ainsi, si vous faites
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
va lancer une requête de base de données à nouveau en arrière-plan pour obtenir les résultats
comprend:
: comprend un joint extérieur gauche entre les deux tableaux. 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">]>
résultera en une table jointe avec tous les enregistrements de la table de commentaires. ainsi 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 va chercher des enregistrements où des commentaires.user_id est nul comme indiqué.
inclut en outre des charges les deux tables dans la mémoire. Ainsi, si vous faites
comment_1 = Comment.includes(:user).first
comment_1.user.age
#=> 24
comme vous pouvez le voir dans comment_1.utilisateur.age charge simplement le résultat de la mémoire sans lancer une requête de base de données fond.
tl; dr
je les contraste de deux façons:
joins - pour la sélection conditionnelle de documents.
comprend - Lors de l'utilisation d'une association sur chaque membre d'un jeu de résultats.
version plus longue
Joins est destiné à filtrer le jeu de résultats provenant de la base de données. Vous utilisez - le pour effectuer des opérations sur votre table. Pensez à ceci comme une clause où 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 obtiendrez les messages dupliqués de nouveau avec les jointures. Mais chaque poste est un poste qui est des commentaires. Vous pouvez corriger cela avec distinct:
Post.joins(:comments).count
=> 10
Post.joins(:comments).distinct.count
=> 2
Dans le contrat, le La méthode includes
s'assurera simplement qu'il n'y a pas de requêtes supplémentaires dans la base de données lors du référencement de la relation (de sorte que nous ne faisons pas de requêtes n + 1)
Post.includes(:comments).count
=> 4 # includes posts without comments so the count might be higher.
La morale est, utiliser joins
lorsque vous voulez faire conditionnelle ensemble des opérations et à l'utilisation includes
lorsque vous allez à l'aide d'une relation de chaque membre d'une collection.
.joins fonctionne comme la jointure de base de données et il joint deux ou plusieurs tables et récupèrent des données sélectionnées à partir du backend(base de données).
.inclut le travail comme une jointure gauche de la base de données. Chargé de tous les enregistrements de gauche, n'ont pas la pertinence de droite du modèle. Il est utilisé pour charger rapidement parce qu'il charge tous les objets associés dans la mémoire. Si nous appelons des associations sur le résultat de requête d'inclusion puis il ne tire pas une requête sur la base de données, il renvoie simplement des données de la mémoire parce qu'il a données déjà chargées en mémoire.
'rejoint' juste utilisé pour joindre des tables et lorsque vous avez appelé les associations sur la rejoint alors il sera de nouveau le feu de la requête (ça veut dire beaucoup de requête feu)
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 de 11 dans ce cas
mais avec 'includes' will eager charge les associations incluses et les ajoute en mémoire (charge toutes les associations sur la première charge) et ne tire pas à nouveau la requête
quand vous obtenez des enregistrements avec includes like @dossier= Utilisateur.comprend (des organisations).où("des organisations.user_id = 1") alors 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.carte organisation.nom} aucune requête de feu