Preload, Eagerload, Includes and Joins
Bài đăng này đã không được cập nhật trong 7 năm
MỞ ĐẦU
Rails với ActiveRecord giúp đỡ cho lập trình viên rất nhiều trong việc truy xuất dữ liệu từ cơ sở dữ liệu quan hệ, đặc biệt là trong trường hợp cần lấy dữ liệu từ các bảng liên kết với nhau bằng việc cung cấp các method tiện ích. Trong Rails có các method như là preload
, eager_load
, includes
, references
và joins
. Các method này đều có cùng mục đích là load data từ các bảng có quan hệ, tuy nhiên mỗi cách lại có cách thức xử lý khác nhau và phù hợp cho các trường hợp khác nhau. Vì vậy trong bài viết này sẽ giới thiệu các method trên, ưu nhược để có thể ứng dụng phù hợp trong từng hoàn cảnh.
Chuẩn bị model
# app/models/student.rb
class Student < ActiveRecord::Base
has_many :courses
has_many :subjects
end
# app/models/course.rb
class Subject < ActiveRecord::Base
belongs_to :student
has_many :courses
end
# app/models/subject.rb
class Course < ActiveRecord::Base
belongs_to :student
belongs_to :subject
end
class CreateStudents < ActiveRecord::Migration[5.0]
def change
create_table :students do |t|
t.string :name
t.timestamps
end
end
end
class CreateSubjects < ActiveRecord::Migration[5.0]
def change
create_table :subjects do |t|
t.string :name
t.timestamps
end
end
end
class CreateCourses < ActiveRecord::Migration[5.0]
def change
create_table :courses do |t|
t.string :name
t.integer :subject_id
t.integer :student_id
t.timestamps
end
end
end
def self.setup
Student.delete_all
Subject.delete_all
Course.delete_all
s = Student.create name: 'Duy'
sub_ruby = Subject.create name: 'ruby'
sub_rails = Subject.create name: 'rails'
sub_ruby = Subject.create name: 'ruby'
sub_js = Subject.create name: 'js'
s.courses.create! name: 'ruby is awesome', subject_id: sub_ruby.id
s.courses.create! name: 'rails is awesome', subject_id: sub_rails.id
s.courses.create! name: 'JavaScript is awesome', subject_id: sub_js.id
s = Student.create name: 'X'
s.courses.create! name: 'Javascript is awesome', subject: Subject.create(name: 'Js')
s = Student.create name: 'User Y'
end
Preload
Preload
load dữ liệu quan hệ qua nhiều câu query riêng rẽ. Ví dụ
Student.preload(:courses).to_a
# =>
SELECT "students".* FROM "students"
SELECT "courses".* FROM "courses" WHERE "courses"."student_id" IN (1, 2, 3, 4, 5)
Đây chính xác cũng là cách thức hoạt động default của includes
. Cũng bởi preload
luôn sinh ra 2 câu lệnh riêng rẽ để load data, nên ta ko thể sử dụng bảng courses
trong điều kiện WHERE
được
Student.preload(:courses).where("courses.name='ruby tut'")
# =>
SQLite3::SQLException: no such column: courses.name:
SELECT "students".* FROM "students" WHERE (courses.name='ruby tut')
Includes
Includes
cũng load data bằng 2 câu query riêng biệt như preload
, tuy nhiên includes
thông minh hơn. Không như preload
chết trong trường hợp điều kiện WHERE
tham chiếu tới bảng quan hệ, includes
xử lý như sau:
Student.includes(:courses).where("courses.name='ruby tut'")
# =>
SELECT "students"."id" AS t0_r0, "students"."name" AS t0_r1, "courses"."id" AS t1_r0,
"courses"."name" AS t1_r1,
"courses"."student_id" AS t1_r2, "courses"."subject_id" AS t1_r3
FROM "students" LEFT OUTER JOIN "courses" ON "courses"."student_id" = "students"."id"
WHERE (courses.name = "ruby tut")
Như ta thấy, includes
chuyển từ 2 câu query thành 1 câu duy nhất dùng LEFT OUTER JOIN
và có thể sử dụng được câu lệnh WHERE
bình thường. Như vậy trong trường họp default, đon giản thì includes
vẫn sử dụng 2 câu query, tuy nhên nếu ta muốn ép
includes
chỉ sử dụng 1 câu query duy nhất, lúc đó cần sử dụng references
Student.includes(:courses).references(:courses)
# =>
SELECT "students"."id" AS t0_r0, "students"."name" AS t0_r1, "courses"."id" AS t1_r0,
"courses"."name" AS t1_r1,
"courses"."student_id" AS t1_r2, "courses"."subject_id" AS t1_r3
FROM "students" LEFT OUTER JOIN "courses" ON "courses"."student_id" = "students"."id"
Eager load
earger_load
thì load data association trong một câu lệnh query duy nhất sử dung LEFT OUTER JOIN
- cũng chính là những gì includes
thực hiện khi điều kiện where
tham chiếu tới thuộc tính của bảng courses
Student.eager_load(:courses)
# =>
SELECT "students"."id" AS t0_r0, "students"."name" AS t0_r1, "courses"."id" AS t1_r0,
"courses"."name" AS t1_r1,
"courses"."student_id" AS t1_r2, "courses"."subject_id" AS t1_r3
FROM "students" LEFT OUTER JOIN "courses" ON "courses"."student_id" = "students"."id"
Joins
Join
load association data sử dụng INNER JOIN
Student.joins(:courses)
# =>
SELECT "students".* FROM "students" INNER JOIN "courses" ON "courses"."student_id" = "students"."id"
Trong câu query trên thì không lấy dữ liệu từ bảng courses
nên sẽ gây ra hiện tượng trùng lắp data.
#<Student id: 9, name: "Duy">
#<Student id: 9, name: "Duy">
#<Student id: 9, name: "Duy">
#<Student id: 10, name: "X">
Để tránh duplicate, ta dùng thêm từ khóa distinct
Student.joins(:courses).select('distinct students.*').to_a
Và nếu cần sử dụng attributes của bảng courses
, ta phải khai báo trong câu lệnh select
records = Student.joins(:courses).select('distinct students.*, courses.name as courses_name').to_a
records.each do |student|
puts student.name
puts student.courses_name
end
KẾT LUẬN
Trên đây mình đã tổng hợp và giới thiệu một số cách load data association khi làm việc với ActiveRecord
trong Rails
. Hy vọng bài viết giúp đỡ được phần nào trong quá trình phát triển phần mềm của các bạn!!!
Nguồn tham khảo
All rights reserved