Rotaie: include vs.: join


345

Questa è più una domanda "perché le cose funzionano in questo modo" piuttosto che una domanda "Non so come fare" ...

Quindi il vangelo sull'estrazione di documenti associati che sai di usare sarà quello di usarlo :include perché otterrai un join ed eviterai un sacco di domande extra:

Post.all(:include => :comments)

Tuttavia, quando si visualizzano i registri, non si verifica alcun join:

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) 

Si sta prendendo una scorciatoia perché tira tutti i commenti in una sola volta, ma non è ancora un join (che è ciò che tutta la documentazione sembra dire). L'unico modo per ottenere un join è utilizzare :joinsinvece di:include :

Post.all(:joins => :comments)

E i registri mostrano:

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

Mi sto perdendo qualcosa? Ho un'app con una mezza dozzina di associazioni e su uno schermo visualizzo i dati di tutte. Sembra che sarebbe meglio avere una query join-ed invece di 6 individui. So che dal punto di vista delle prestazioni non è sempre meglio fare un join piuttosto che singole query (in effetti se si passa dal tempo trascorso, sembra che le due singole query sopra siano più veloci del join), ma dopo tutti i documenti Ho letto sono sorpreso di vedere :includenon funziona come pubblicizzato.

Forse Rails è a conoscenza del problema delle prestazioni e non partecipa se non in alcuni casi?


3
se stavi utilizzando una versione precedente di Rails, ti preghiamo di indicarlo tramite tag o nel corpo della domanda. Altrimenti, se stai usando Rails 4 NOW, lo è includes(per chiunque legga questo)
onebree

Inoltre ora c'è: precarico e: eager_load blog.bigbinary.com/2013/07/01/…
CJW

Risposte:


179

Sembra che la :includefunzionalità sia stata modificata con Rails 2.1. Rails era solito fare l'unione in tutti i casi, ma per motivi di prestazioni è stato modificato per utilizzare più query in alcune circostanze. Questo post sul blog di Fabio Akita contiene alcune buone informazioni sulla modifica (vedere la sezione intitolata "Caricamento desideroso ottimizzato").



Questo è molto utile, grazie. Vorrei però che ci fosse un modo per costringere Rails a fare l'unione anche senza un "dove" che lo richiedesse. In alcuni casi, sai che il join sarà più efficiente e non incorrerà in rischi di duplicazione.
Jonathan Swartz,


@JonathanSwartz Sembra che la nuova versione di Rails lo supporti usando eagerload . Grazie per il link NathanLong
rubyprince,

92

.joinsunisce le tabelle e restituisce i campi selezionati. se si chiamano associazioni sul risultato della query sui join, verranno nuovamente attivate le query del database

:includesimpaziente caricherà le associazioni incluse e le aggiungerà in memoria. :includescarica tutti gli attributi delle tabelle incluse. Se si chiamano le associazioni per includere il risultato della query, non genererà alcuna query


71

La differenza tra join e include è che l'uso dell'istruzione include genera una query SQL molto più grande caricando in memoria tutti gli attributi delle altre tabelle.

Ad esempio, se hai una tabella piena di commenti e usi a: joins => users per inserire tutte le informazioni dell'utente per scopi di ordinamento, ecc. Funzionerà bene e richiederà meno tempo di: include, ma dì che vuoi visualizzare il commento insieme al nome utente, e-mail, ecc. Per ottenere le informazioni usando: join, dovrà effettuare query SQL separate per ogni utente che recupera, mentre se hai usato: includi queste informazioni è pronto per l'uso.

Ottimo esempio:

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


55

Recentemente ho letto di più sulla differenza tra :joinse:includes nelle rotaie. Ecco una spiegazione di ciò che ho capito (con esempi :))

Considera questo scenario:

  • Un utente ha molti commenti e un commento appartiene a un utente.

  • Il modello utente ha i seguenti attributi: Nome (stringa), Età (numero intero). Il modello di commento ha i seguenti attributi: Content, user_id. Per un commento un user_id può essere nullo.

join:

: join esegue un join interno tra due tabelle. così

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

recupererà tutti i record in cui user_id (della tabella dei commenti) è uguale a user.id (tabella degli utenti). Quindi se lo fai

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

#=> <ActiveRecord::Relation []>

Otterrai un array vuoto come mostrato.

Inoltre i join non caricano la tabella unita in memoria. Quindi se lo fai

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

Come vedi, comment_1.user.age eseguirà di nuovo una query del database in background per ottenere i risultati

include:

: include esegue un join esterno sinistro tra le due tabelle. così

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

si tradurrà in una tabella unita con tutti i record dalla tabella dei commenti. Quindi se lo fai

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

recupererà i record in cui commenti.utente_id è zero come mostrato.

Inoltre include carica entrambe le tabelle nella memoria. Quindi se lo fai

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Come puoi notare comment_1.user.age carica semplicemente il risultato dalla memoria senza attivare una query del database in background.


È per Rails 4?
onebree,

@HunterStevens: Sì, lo è
Aaditi Jain,

54

Oltre alle considerazioni sulle prestazioni, c'è anche una differenza funzionale. Quando si uniscono commenti, si richiedono messaggi che contengono commenti: un join interno per impostazione predefinita. Quando includi commenti, chiedi tutti i post, un join esterno.


10

tl; dr

Li contrasto in due modi:

join : per la selezione condizionale dei record.

include : quando si utilizza un'associazione su ciascun membro di un set di risultati.

Versione più lunga

Joins ha lo scopo di filtrare il set di risultati proveniente dal database. Lo usi per fare operazioni sul tavolo. Pensa a questo come a una clausola where che esegue la teoria degli insiemi.

Post.joins(:comments)

equivale a

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

Tranne il fatto che se ci sono più di un commento riceverai post duplicati con i join. Ma ogni post sarà un post con commenti. È possibile correggere questo con distinti:

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

Nel contratto, il includesmetodo farà semplicemente in modo che non ci siano query aggiuntive sul database quando si fa riferimento alla relazione (in modo da non fare n + 1 query)

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

La morale è, usare joinsquando si desidera eseguire operazioni su insiemi condizionali e utilizzare includesquando si intende utilizzare una relazione su ciascun membro di una raccolta.


Questo distinctmi prende ogni volta. Grazie!
Ben Hull,

4

.joins funziona come join del database e unisce due o più tabelle e recupera i dati selezionati dal backend (database).

.includes funziona come join sinistro del database. Ha caricato tutti i record del lato sinistro, non ha rilevanza per il modello del lato destro. Viene utilizzato per il caricamento desideroso perché carica tutti gli oggetti associati in memoria. Se chiamiamo associazioni sul risultato della query include, non genera una query sul database, ma restituisce semplicemente i dati dalla memoria perché ha già caricato i dati in memoria.


0

'join' è stato appena utilizzato per unire le tabelle e quando si chiamavano associazioni sui join, si attiverà nuovamente la query (significa che molte query verranno attivate)

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)

il numero totale di SQL è 11 in questo caso

Ma con 'Includes' impazierà di caricare le associazioni incluse e le aggiungerà in memoria (caricherà tutte le associazioni al primo caricamento) e non genererà più query

quando ottieni record con include come @ records = User.includes (: organisations) .where ("organisations.user_id = 1"), la query sarà

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} nessuna query verrà attivata

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.