Rieles: incluye vs.: se une

345

Esta es más una pregunta de "por qué las cosas funcionan de esta manera" en lugar de una pregunta de "No sé cómo hacer esto" ...

Entonces, el evangelio sobre extraer los registros asociados que sabe que va a usar es :includeporque obtendrá una combinación y evitará un montón de consultas adicionales:

Post.all(:include => :comments)

Sin embargo, cuando nos fijamos en los registros, no se produce una unión:

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) 

Se está tomando un atajo porque tira todos los comentarios a la vez, pero todavía no es una unión (que es lo que toda la documentación parece decir). La única forma en que puedo obtener una combinación es usar en :joinslugar de :include:

Post.all(:joins => :comments)

Y los registros muestran:

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

¿Me estoy perdiendo de algo? Tengo una aplicación con media docena de asociaciones y en una pantalla visualizo datos de todas ellas. Parece que sería mejor tener una consulta unida en lugar de 6 individuos. Sé que, en términos de rendimiento, no siempre es mejor hacer una unión en lugar de consultas individuales (de hecho, si va por el tiempo invertido, parece que las dos consultas individuales anteriores son más rápidas que la unión), pero después de todos los documentos He estado leyendo, me sorprende ver que :includeno funciona como se anuncia.

¿Quizás Rails es consciente del problema de rendimiento y no se une excepto en ciertos casos?

Rob Cameron
fuente
3
Si estaba utilizando una versión anterior de Rails, indíquelo mediante etiquetas o en el cuerpo de su pregunta. De lo contrario, si está utilizando Rails 4 NOW, es includes(para cualquiera que lea esto)
onebree
También hay ahora: preload y: eager_load blog.bigbinary.com/2013/07/01/…
CJW

Respuestas:

179

Parece que la :includefuncionalidad se cambió con Rails 2.1. Rails solía hacer la unión en todos los casos, pero por razones de rendimiento se cambió para usar múltiples consultas en algunas circunstancias. Esta publicación de blog de Fabio Akita tiene buena información sobre el cambio (consulte la sección titulada "Carga optimizada optimizada").

Greg Campbell
fuente
Esto es muy útil, gracias. Sin embargo, desearía que hubiera una manera de obligar a Rails a hacer la unión incluso sin un "dónde" que lo requiera. En algunos casos, sabe que la unión será más eficiente y no incurrirá en riesgo de duplicación.
Jonathan Swartz el
1
Ver también: blog.bigbinary.com/2013/07/01/…
Nathan Long
@ JonathanSwartz Parece que la nueva versión de Rails admite esto usando eagerload . Gracias por el enlace NathanLong
rubyprince
92

.joinssolo se unirá a las tablas y traerá los campos seleccionados a cambio. si llama a asociaciones en el resultado de la consulta de combinaciones, volverá a generar consultas en la base de datos

:includesansioso cargará las asociaciones incluidas y las agregará a la memoria. :includescarga todos los atributos de tablas incluidos. Si llama a asociaciones en el resultado de la consulta de inclusión, no activará ninguna consulta

Prem
fuente
71

La diferencia entre unir e incluir es que el uso de la declaración de inclusión genera una consulta SQL mucho más grande que carga en la memoria todos los atributos de las otras tablas.

Por ejemplo, si tiene una tabla llena de comentarios y usa un: une => usuarios para obtener toda la información del usuario para clasificar, etc. funcionará bien y tomará menos tiempo que: incluir, pero digamos que desea mostrar el comentario junto con el nombre de usuario, correo electrónico, etc. Para obtener la información usando: une, tendrá que hacer consultas SQL por separado para cada usuario que busque, mientras que si usó: incluir esta información está lista para usar.

Gran ejemplo:

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

Holden
fuente
55

Recientemente estuve leyendo más sobre la diferencia entre :joinsy :includesen los rieles. Aquí hay una explicación de lo que entendí (con ejemplos :))

Considere este escenario:

  • Un usuario tiene muchos comentarios y un comentario pertenece a un usuario.

  • El modelo de usuario tiene los siguientes atributos: Nombre (cadena), Edad (entero). El modelo Comentario tiene los siguientes atributos: Contenido, id_usuario. Para un comentario, un user_id puede ser nulo.

Uniones:

: une realiza una unión interna entre dos tablas. Así

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

buscará todos los registros donde user_id (de la tabla de comentarios) sea igual a user.id (tabla de usuarios). Entonces si lo haces

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

#=> <ActiveRecord::Relation []>

Obtendrá una matriz vacía como se muestra.

Además, las uniones no cargan la tabla unida en la memoria. Entonces si lo haces

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

Como puede ver, comment_1.user.ageactivará nuevamente una consulta de base de datos en segundo plano para obtener los resultados

Incluye:

: Incluye realiza una unión externa izquierda entre las dos tablas. Así

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

dará como resultado una tabla unida con todos los registros de la tabla de comentarios. Entonces si lo haces

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

buscará registros donde comments.user_id es nulo como se muestra.

Además incluye cargas tanto las tablas en la memoria. Entonces si lo haces

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Como puede observar, comment_1.user.age simplemente carga el resultado de la memoria sin disparar una consulta de base de datos en segundo plano.

Aaditi Jain
fuente
¿Es esto para Rails 4?
onebree
@HunterStevens: Sí, lo es
Aaditi Jain
54

Además de las consideraciones de rendimiento, también hay una diferencia funcional. Cuando une comentarios, está solicitando publicaciones que tengan comentarios, una unión interna de forma predeterminada. Cuando incluye comentarios, solicita todas las publicaciones: una combinación externa.

Brian Maltzan
fuente
10

tl; dr

Los comparo de dos maneras:

une : para la selección condicional de registros.

incluye : cuando se utiliza una asociación en cada miembro de un conjunto de resultados.

Versión más larga

Uniones está destinado a filtrar el conjunto de resultados proveniente de la base de datos. Lo usa para realizar operaciones de configuración en su tabla. Piense en esto como una cláusula where que realiza la teoría de conjuntos.

Post.joins(:comments)

es lo mismo que

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

Excepto que si hay más de un comentario, recibirá publicaciones duplicadas con las uniones. Pero cada publicación será una publicación que tenga comentarios. Puede corregir esto con distintos:

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

En el contrato, el includesmétodo simplemente se asegurará de que no haya consultas adicionales de la base de datos al hacer referencia a la relación (para que no hagamos consultas n + 1)

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

La moraleja es, úsela joinscuando desee realizar operaciones de conjuntos condicionales y úsela includescuando vaya a utilizar una relación en cada miembro de una colección.

Kevin Choubacha
fuente
Eso distinctme atrapa todo el tiempo. ¡Gracias!
Ben Hull
4

.joins funciona como unión de base de datos y une dos o más tablas y obtiene datos seleccionados del backend (base de datos).

.incluye el trabajo como combinación izquierda de la base de datos. Cargó todos los registros del lado izquierdo, no tiene relevancia del modelo del lado derecho. Se utiliza para cargar con impaciencia porque carga todos los objetos asociados en la memoria. Si llamamos a asociaciones en el resultado de la consulta de inclusión, entonces no activa una consulta en la base de datos, simplemente devuelve datos de la memoria porque ya ha cargado datos en la memoria.


fuente
0

'Uniones' solo se usaba para unir tablas y cuando llamaste a asociaciones en uniones, volverá a activar la consulta (significa que se activarán muchas consultas)

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)

el número total de SQL es 11 en este caso

Pero con 'incluye' ansioso cargará las asociaciones incluidas y las agregará en la memoria (cargará todas las asociaciones en la primera carga) y no activará nuevamente la consulta

cuando obtiene registros con incluye como @ records = User.includes (: organizaciones) .where ("organisations.user_id = 1") entonces la consulta será

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 se activará ninguna consulta

Thorin
fuente