LEFT OUTER łączy w Rails 3

86

Mam następujący kod:

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

co ma na celu znalezienie wszystkich postów i zwrócenie ich oraz powiązanych użytkowników i blogów. Jednak użytkownicy są opcjonalni, co oznacza, że ​​to, INNER JOINco :joinsgeneruje, nie zwraca wielu rekordów.

Jak użyć tego do wygenerowania LEFT OUTER JOINzamiast tego?

Neil Middleton
źródło
Zobacz także LEFT OUTER JOIN w Rails 4
Yarin

Odpowiedzi:

111
@posts = Post.joins("LEFT OUTER JOIN users ON users.id = posts.user_id").
              joins(:blog).select
Neil Middleton
źródło
3
co by było, gdybyś chciał tylko postów, które nie miały użytkownika?
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
Czy opcja Select nie potrzebuje parametru? Nie powinno tak być select('posts.*')?
Kevin Sylvestre
W Railsach 3 jest to jedyny sposób, aby mieć prawdziwą kontrolę nad połączeniami i wiedzieć dokładnie, co się dzieje.
Joshua Pinter,
75

Możesz to zrobić includes zgodnie z opisem w przewodniku po Railsach :

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

Prowadzi do:

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
źródło
14
Z moich testów includesnie robi złączenia, ale osobne zapytanie, aby uzyskać skojarzenie. Więc unika N + 1, ale nie w taki sam sposób, jak JOIN, w którym rekordy są pobierane w jednym zapytaniu.
Kris
7
@Kris W pewnym sensie masz rację. Jest to coś, na co musisz uważać, ponieważ ta includesfunkcja robi jedno i drugie, w zależności od kontekstu, w którym jej używasz. Przewodnik po Railsach wyjaśnia to lepiej niż ja, gdybym przeczytał całą sekcję 12: Guides.rubyonrails.org/ …
WuTangTan
4
To tylko częściowo odpowiada na pytanie, ponieważ includeswygeneruje 2 zapytania zamiast jednego, JOINjeśli nie potrzebujesz WHERE.
Rodrigue,
14
Spowoduje to wygenerowanie ostrzeżenia w Rails 4, chyba że również dodasz references(:comments). Dodatkowo spowoduje to, że wszystkie zwrócone komentarze będą chętnie ładowane do pamięci z powodu includes, co prawdopodobnie nie jest tym, czego chcesz.
Derek Prior
2
Aby ten jeszcze bardziej „Railsy”: Post.includes(:comments).where(comments: {visible: true}). W ten sposób nie musisz też używać references.
michael
11

Jestem wielkim fanem piskląt :

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

Obsługuje zarówno połączenia, jak inneri outerłączenia, a także możliwość określenia klasy / typu dla polimorficznych relacji przynależności_do.

plainjimbo
źródło
10

Zastosowanie eager_load:

@posts = Post.eager_load(:user)
Ricardo
źródło
8

Domyślnie po przekazaniu ActiveRecord::Base#joinsnazwanego powiązania wykona INNER JOIN. Będziesz musiał przekazać ciąg reprezentujący LEWE ZEWNĘTRZNE POŁĄCZENIE.

Z dokumentacji :

:joins- Albo fragment SQL dla dodatkowych łączeń, takich jak „ LEFT JOIN comments ON comments.post_id = id” (rzadko potrzebne), nazwane asocjacje w tej samej formie :include, co w przypadku opcji, które wykonają INNER JOIN na skojarzonej tabeli (tabelach) lub tablica zawierająca mieszaninę obu łańcuchów i nazwane stowarzyszenia.

Jeśli wartość jest ciągiem, rekordy zostaną zwrócone tylko do odczytu, ponieważ będą miały atrybuty, które nie odpowiadają kolumnom tabeli. Przejdź :readonly => falsedo zastąpienia.

DBA
źródło
7

W activerecord istnieje metoda left_outer_joins. Możesz go używać w ten sposób:

@posts = Post.left_outer_joins(:user).joins(:blog).select
Ahmad Hussain
źródło
1
Wydaje się, że to nie istnieje w Railsach 3, o co prosi plakat.
cesoid
Poprawny; zostało to wprowadzone w Railsach 5.0.0.
Ollie Bennett
4

Dobra wiadomość, Rails 5 obsługuje teraz LEFT OUTER JOIN. Twoje zapytanie wyglądałoby teraz następująco:

@posts = Post.left_outer_joins(:user, :blog)
Dex
źródło
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
źródło