LEFT OUTER rejoint les rails 3

86

J'ai le code suivant:

@posts = Post.joins(:user).joins(:blog).select

qui est destiné à trouver tous les articles et à les renvoyer ainsi qu'aux utilisateurs et blogs associés. Cependant, les utilisateurs sont facultatifs, ce qui signifie que le INNER JOINqui :joinsgénère ne renvoie pas beaucoup d'enregistrements.

Comment utiliser ceci pour générer un à la LEFT OUTER JOINplace?

Neil Middleton
la source
Voir aussi LEFT OUTER JOIN in Rails 4
Yarin

Réponses:

111
@posts = Post.joins("LEFT OUTER JOIN users ON users.id = posts.user_id").
              joins(:blog).select
Neil Middleton
la source
3
Et si vous ne vouliez que les messages qui n'avaient pas d'utilisateur?
mcr
24
@mcr@posts = Post.joins("LEFT OUTER JOIN users ON users.id = posts.user_id").joins(:blog).where("users.id IS NULL").select
Linus Oleander
1
La sélection n'a-t-elle pas besoin d'un paramètre? Cela ne devrait-il pas être select('posts.*')?
Kevin Sylvestre
Dans Rails 3, c'est le seul moyen d'avoir un véritable contrôle sur vos jointures et de savoir exactement ce qui se passe.
Joshua Pinter
75

Vous pouvez le faire avec includes comme indiqué dans le guide Rails :

Post.includes(:comments).where(comments: {visible: true})

Résulte en:

SELECT "posts"."id" AS t0_r0, ...
       "comments"."updated_at" AS t1_r5
FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
WHERE (comments.visible = 1)
WuTangTan
la source
14
De mes tests includesne fait pas une jointure, mais une requête distincte pour obtenir l'assosiation. Cela évite donc N + 1, mais pas de la même manière qu'un JOIN où les enregistrements sont récupérés en une seule requête.
Kris
7
@Kris Vous avez raison, d'une certaine manière. C'est quelque chose dont vous devez faire attention car la includesfonction fait les deux, selon le contexte dans lequel vous l'utilisez. Le guide Rails l'explique mieux que moi si vous lisiez l'intégralité de la section 12: guides.rubyonrails.org/ …
WuTangTan
4
Cela ne répond que partiellement à la question car includescela générera 2 requêtes au lieu d'une JOINsi vous n'avez pas besoin de WHERE.
Rodrigue
14
Cela générera un avertissement dans Rails 4 sauf si vous ajoutez également references(:comments). En outre, cela entraînera le chargement hâtif de tous les commentaires renvoyés en mémoire en raison de includes, ce qui n'est peut-être pas ce que vous souhaitez.
Derek Prior
2
Pour rendre cela encore plus « Railsy »: Post.includes(:comments).where(comments: {visible: true}). De cette façon, vous n'avez pas non plus besoin d'utiliser references.
michael
11

Je suis un grand fan de la gemme squeel :

Post.joins{user.outer}.joins{blog}

Il prend en charge à la fois inneret les outerjointures, ainsi que la possibilité de spécifier une classe / un type pour les relations polymorphes appart_to.

plainjimbo
la source
10

Utilisez eager_load:

@posts = Post.eager_load(:user)
Ricardo
la source
8

Par défaut, lorsque vous passez ActiveRecord::Base#joinsune association nommée, elle effectuera une INNER JOIN. Vous devrez passer une chaîne représentant votre JOINTURE EXTÉRIEURE GAUCHE.

De la documentation :

:joins- Soit un fragment SQL pour des jointures supplémentaires comme " LEFT JOIN comments ON comments.post_id = id" (rarement nécessaire), des associations nommées sous la même forme que celle utilisée pour l' :includeoption, qui effectuera un INNER JOIN sur la ou les tables associées, soit un tableau contenant un mélange des deux chaînes et les associations nommées.

Si la valeur est une chaîne, les enregistrements seront renvoyés en lecture seule car ils auront des attributs qui ne correspondent pas aux colonnes de la table. Passer :readonly => falsepour passer outre.

DBA
la source
7

Il y a une left_outer_joins méthode activerecord. Vous pouvez l'utiliser comme ceci:

@posts = Post.left_outer_joins(:user).joins(:blog).select
Ahmad Hussain
la source
1
Cela ne semble pas exister dans Rails 3, c'est ce que l'affiche demande.
cesoid
Correct; cela a été introduit dans Rails 5.0.0.
Ollie Bennett
4

Bonne nouvelle, Rails 5 prend désormais en charge LEFT OUTER JOIN. Votre requête ressemblerait maintenant à:

@posts = Post.left_outer_joins(:user, :blog)
Dex
la source
0
class User < ActiveRecord::Base
     has_many :friends, :foreign_key=>"u_from",:class_name=>"Friend"
end

class Friend < ActiveRecord::Base
     belongs_to :user
end


friends = user.friends.where(:u_req_status=>2).joins("LEFT OUTER JOIN users ON users.u_id = friends.u_to").select("friend_id,u_from,u_to,u_first_name,u_last_name,u_email,u_fbid,u_twtid,u_picture_url,u_quote")
Jigar Bhatt
la source