Rails: include vs.: joins

Jest to bardziej pytanie "dlaczego rzeczy działają w ten sposób", a nie "Nie wiem, jak to zrobić"...

Więc Ewangelia na wyciąganie powiązanych płyt, które wiesz, że będziesz używać, to używać :include ponieważ dostaniesz join i unikniesz całej masy dodatkowych zapytań:

Post.all(:include => :comments)

Jednak gdy spojrzysz na logi, nie dzieje się żadne połączenie:

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) 

To jest biorąc Skrót, ponieważ ciągnie wszystkie komentarze na raz, ale nadal nie jest join (co wydaje się mówić cała dokumentacja). Jedynym sposobem na uzyskanie połączenia jest użycie :joins zamiast :include:

Post.all(:joins => :comments)

I dzienniki pokazują:

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

Czy coś przeoczyłem? Mam aplikację z pół tuzina skojarzeń i na jednym ekranie wyświetlam dane z nich wszystkich. Wydaje się, że lepiej byłoby mieć jedno zapytanie join-ed zamiast 6 osób. Wiem, że pod względem wydajności nie zawsze lepiej jest zrobić join zamiast pojedynczych zapytań (w rzeczywistości, jeśli idziesz według czasu spędzonego, wygląda na to, że dwa pojedyncze zapytania powyżej są szybsze niż join), ale po wszystkich docs czytałem jestem zaskoczony, że :include nie działa jak reklamowane.

Może Rails jest świadomy kwestii wydajności i nie łączy się z wyjątkiem pewnych przypadków?

Author: Simone Carletti, 2009-07-30

8 answers

Wygląda na to, że funkcjonalność :include została zmieniona w Rails 2.1. Rails we wszystkich przypadkach wykonywało połączenie, ale ze względu na wydajność zostało zmienione, aby w niektórych okolicznościach używać wielu zapytań. Ten wpis na blogu autorstwa Fabio Akity zawiera kilka dobrych informacji na temat zmiany (patrz sekcja zatytułowana "zoptymalizowane Eager Loading").

 167
Author: Greg Campbell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-10 19:26:59

.joins will po prostu dołącza do tabel i przynosi wybrane pola w zamian. jeśli wywołasz Asocjacje po połączeniu wyniku zapytania, odpali ono ponownie zapytania do bazy danych

:includes z chęcią załaduje dołączone skojarzenia i doda je do pamięci. :includes wczytuje wszystkie dołączone atrybuty tabel. Jeśli wywołasz Asocjacje w wyniku include query, nie wywoła ono żadnych zapytań

Mój Blog post zawiera szczegółowe wyjaśnienie różnic

 79
Author: Prem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-26 19:13:18

Różnica między połączeniami i include polega na tym, że użycie instrukcji include generuje znacznie większe zapytanie SQL ładujące do pamięci wszystkie atrybuty z drugiej tabeli(tabel).

Na przykład, jeśli masz tabelę pełną komentarzy i używasz a: joins = > users, aby pobrać wszystkie informacje o użytkowniku do sortowania itp., będzie to działać dobrze i zajmie mniej czasu niż: include, ale powiedz, że chcesz wyświetlić komentarz wraz z nazwą użytkownika, e-mailem itp. Aby uzyskać informacje za pomocą : joins, będzie musiał wykonać osobne zapytania SQL dla każdego pobranego użytkownika, podczas gdy jeśli użyłeś :include ta informacja jest gotowa do użycia.

Świetny przykład:

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

 67
Author: holden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-29 09:20:40

Poza względami wydajności, istnieje również różnica funkcjonalna. Dołączając komentarze, pytasz o posty, które mają komentarze - domyślnie dołączenie wewnętrzne. Kiedy dodajesz komentarze, prosisz o wszystkie posty - zewnętrzne dołączenie.

 50
Author: Brian Maltzan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-30 16:12:08

Ostatnio czytałem więcej o różnicy między :joins i :includes w rails. Oto wyjaśnienie tego, co zrozumiałem (z przykładami :))

Rozważ ten scenariusz:

  • Użytkownik ma_many komentarzy i KOMENTARZ należy do użytkownika.

  • Model użytkownika ma następujące atrybuty: Name( string), Age (integer). Model komentarza ma następujące atrybuty: Content, user_id. Dla komentarza user_id może być null.

Dołączył:

:joins wykonuje wewnętrzne połączenie pomiędzy dwiema tabelami. Tak więc

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

Pobierze wszystkie rekordy, w których user_id (tabeli komentarzy) jest równy user.id (tabela użytkowników). zatem jeśli zrobisz

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

#=> <ActiveRecord::Relation []>

Otrzymasz pustą tablicę, jak pokazano.

Ponadto joins nie ładuje połączonej tabeli do pamięci. Zatem jeśli zrobisz

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

Jak widzisz, comment_1.user.age ponownie odpali zapytanie do bazy danych w tle aby uzyskać wyniki

Zawiera:

: includes wykonuje LEFT outer join pomiędzy dwoma tabelami. Tak więc

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

Spowoduje połączoną tabelę ze wszystkimi rekordami z tabeli komentarzy. zatem jeśli zrobisz

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

Pobiera rekordy, gdzie komentarze.user_id jest nil, jak pokazano.

Ponadto zawiera Ładowanie obu tabel w pamięci. Zatem jeśli zrobisz

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Jak można zauważyć komenta_1.użytkownik.wiek po prostu ładuje wynik z pamięci bez uruchamiania zapytania bazy danych w tle.

 45
Author: Aaditi Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-01 08:21:40

tl; dr

Kontrastuję je na dwa sposoby:

Joins - do warunkowego wyboru rekordów.

Includes - przy użyciu asocjacji na każdym członie zbioru wynikowego.

dłuższa wersja

Joins służy do filtrowania zbioru wyników pochodzących z bazy danych. Używasz go do ustawiania operacji na stole. Pomyśl o tym jako o klauzuli where, która wykonuje teorię mnogości.

Post.joins(:comments)

Jest tym samym as

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

Z wyjątkiem tego, że jeśli jest więcej niż jeden komentarz, otrzymasz duplikaty postów z powrotem z dołączonymi. Ale każdy post będzie postem, który ma komentarze. Można to skorygować za pomocą distinct:

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

W umowie, metoda includes po prostu upewni się, że nie ma dodatkowych zapytań do bazy danych podczas odwoływania się do relacji (tak, że nie robimy n + 1 zapytań)

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

Morał jest taki, że użyj joins, Gdy chcesz wykonać operacje zestawów warunkowych i użyj includes Kiedy będziesz używać relacji na każdym członie kolekcji.

 5
Author: Kevin Choubacha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-04 23:58:18

.joins działa jako database join i łączy dwie lub więcej tabel i pobiera wybrane dane z backendu (bazy danych).

.zawiera pracę jako lewy łącznik bazy danych. Załadował wszystkie rekordy lewej strony, nie ma znaczenia modelu prawej strony. Jest on używany do eager loading, ponieważ ładuje wszystkie powiązane obiekty w pamięci. Jeśli wywołamy Asocjacje na include query result to nie odpali zapytania na bazie danych, tylko zwróci dane z pamięci ponieważ już załadował dane w pamięć.

 4
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-19 14:22:57

'joins' używane tylko do łączenia tabel i kiedy wywołujesz skojarzenia na joins, to ponownie odpali zapytanie (oznacza to, że wiele zapytań odpali)

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)

Całkowita liczba SQL jest 11 w tym przypadku

Ale z 'includes' załaduje dołączone skojarzenia i doda je do pamięci (załaduje wszystkie skojarzenia przy pierwszym załadowaniu) i nie odpali ponownie zapytania

Kiedy dostajesz płyty z takimi jak @ records= User.obejmuje (: organizacje).gdzie ("organizacje.user_id = 1") następnie zapytanie będzie

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} no query will fire

 0
Author: Thorin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-24 08:31:30