Joins vs Includes (Español)
A veces, includes
y joins
pueden resultar confusos porque hay escenarios donde parecen ser intercambiables, pero hay razones específicas para usar uno o el otro.
joins
joins
nos permite especificar una relación a incluirse en la consulta. La consulta resultante incluirá una cláusula JOIN
(SQL).
Digamos que tenemos estos dos modelos, Empleado (Employee
) que tiene una Computadora (Computer
).
#app/models/employee.rb
class Employee < ActiveRecord::Base
has_one :computer
end
#app/models/computer.rb
class Computer < ActiveRecord::Base
belongs_to :employee
end
Si queremos conocer todos los empleados cuyo nombre sea Fernando y tengan una computadora Dell, podemos usar joins
.
Employee.
joins(:computer).
where(name: 'Fernando', computers: { brand: 'Dell' })
Esta consulta funciona bien y regresa todos los empleados que cunmplan con las condiciones.
El SQL será algo como esto:
SELECT "employees".*
FROM "employees"
INNER JOIN "computers" ON "computers"."employee_id" = "employees"."id"
WHERE "employees"."name" = 'Fernando' AND "computers"."brand" = 'Dell'
includes
Como explicamos en un post previo, si queremos cargar todos los empleados y todas sus computadoras en una única consulta, podemos usar includes
,
Employee.includes(:computer)
De esta manera, Rails golpea la base de datos sólo una vez, y carga todos los datos de empleados y computadoras. Puede que la confusión empiece aquí, porque al usar includes
podemos también realizar algunas condicionales en la cláusula where
. Por ejemplo:
Employee.
includes(:computer).
where(name: 'Fernando', computers: { brand: 'Dell' })
Como podemos ver, es posible incluir condicionales que afecten ambas asociaciones, tal como lo hicimos con joins
.
Sin embargo, el SQL resultante es diferente. Veamos:
SELECT "employees"."id" AS t0_r0,
"employees"."name" AS t0_r1,
"employees"."created_at" AS t0_r2,
"employees"."updated_at" AS t0_r3,
"computers"."id" AS t1_r0,
"computers"."brand" AS t1_r1,
"computers"."model" AS t1_r2,
"computers"."employee_id" AS t1_r3,
"computers"."created_at" AS t1_r4,
"computers"."updated_at" AS t1_r5
FROM "employees"
LEFT OUTER JOIN "computers" ON "computers"."employee_id" = "employees"."id"
WHERE "employees"."name" = 'Fernando' AND "computers"."brand" = 'Dell'
Como vemos, includes
agrega todos los campos de la computadora, como se espera, mientras que joins
no lo hace. Y esa pequeña diferencia es la que debemos tenee en cuenta.
Cuando sólo queremos filtrar los resultado de una consulta basados en un campo que pertenece a una relación secundaria, pero no vamos a usar datos de esa relación más adelante, debemos usar joins
. De otra forma, cargaremos muchos datos que no vamos a usar.
Ejemplo:
employees = Employee.
joins(:computer).
where(name: 'Fernando', computer: { brand: 'Dell' })
puts employees.map(&:name)
En la otra mano, si necesitamos usar datos de la relación secundaria, entonces includes
es obligatorio, de otra manera, tendremos poblemas de consultas N+1.
Ejemplo:
employees = Employee.
joins(:computer).
where(name: 'Fernando', computer: { brand: 'Dell' })
puts employees.map{|e| "#{e.full_name} has a Dell #{e.computer.model}" }
No es tan confuso después de todo, ¿cierto?