LEFT OUTER junta-se ao Rails 3

86

Eu tenho o seguinte código:

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

que tem como objetivo localizar todas as postagens e retorná-las, bem como aos usuários e blogs associados. No entanto, os usuários são opcionais, o que significa que o INNER JOINque :joinsgera não está retornando muitos registros.

Como faço para usar isso para gerar um LEFT OUTER JOIN?

Neil Middleton
fonte
Veja também LEFT OUTER JOIN no Rails 4
Yarin

Respostas:

111
@posts = Post.joins("LEFT OUTER JOIN users ON users.id = posts.user_id").
              joins(:blog).select
Neil Middleton
fonte
3
e se você quisesse apenas as postagens que não tinham usuário?
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
Não precisa selecionar um parâmetro? Não deveria ser assim select('posts.*')?
Kevin Sylvestre
No Rails 3, esta é a única maneira de ter controle verdadeiro sobre suas junções e saber exatamente o que está acontecendo.
Joshua Pinter
75

Você pode fazer isso includes conforme documentado no guia Rails :

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

Resulta em:

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
fonte
14
Dos meus testes includesnão faço uma junção, mas uma consulta separada para obter a associação. Portanto, ele evita N + 1, mas não da mesma forma que um JOIN, onde os registros são buscados em uma consulta.
Kris
7
@Kris Você está certo, de certa forma. É algo que você precisa observar porque a includesfunção faz as duas coisas, dependendo do contexto em que você está usando. O guia Rails explica isso melhor do que eu poderia se você lesse toda a seção 12: guias.rubyonrails.org/ …
WuTangTan
4
Isso responde apenas parcialmente à pergunta porque includesirá gerar 2 consultas em vez de um JOINse você não precisar do WHERE.
Rodrigue
14
Isso irá gerar um aviso no Rails 4, a menos que você também adicione references(:comments). Além disso, isso fará com que todos os comentários retornados sejam carregados rapidamente na memória devido ao includes, o que possivelmente não é o que você deseja.
Derek Prior
2
Para tornar isso ainda mais "Railsy": Post.includes(:comments).where(comments: {visible: true}). Dessa forma, você também não precisa usar references.
michael
11

Sou um grande fã da joia do squeel :

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

Ele suporta junções innere outerjunções, bem como a capacidade de especificar uma classe / tipo para relacionamentos polimórficos belongs_to.

plainjimbo
fonte
10

Use eager_load:

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

Por padrão, quando você passa ActiveRecord::Base#joinsuma associação nomeada, ela executa um INNER JOIN. Você terá que passar uma string representando seu LEFT OUTER JOIN.

Da documentação :

:joins- Um fragmento SQL para junções adicionais como " LEFT JOIN comments ON comments.post_id = id" (raramente necessário), associações nomeadas no mesmo formato usado para a :includeopção, que executará um INNER JOIN na (s) tabela (s) associada (s), ou uma matriz contendo uma mistura de ambas as strings e associações nomeadas.

Se o valor for uma string, os registros serão retornados como somente leitura, pois terão atributos que não correspondem às colunas da tabela. Passar :readonly => false para substituir.

DBA
fonte
7

Existe um método left_outer_joins em activerecord. Você pode usá-lo assim:

@posts = Post.left_outer_joins(:user).joins(:blog).select
Ahmad Hussain
fonte
1
Isso parece não existir no Rails 3, que é o que o autor do post está pedindo.
cesóide
Corrigir; isso foi introduzido no Rails 5.0.0.
Ollie Bennett de
4

Boas notícias, Rails 5 agora suporta LEFT OUTER JOIN. Sua consulta agora seria semelhante a:

@posts = Post.left_outer_joins(:user, :blog)
Dex
fonte
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
fonte