Một số mẹo viết câu truy vấn hiệu quả (tiếp)
Bài đăng này đã không được cập nhật trong 7 năm
Ở bài trước, mình có giới thiệu một số cách để tối ưu hóa câu truy vấn ( link bài trước ) Ở bài này mình giới thiệu thêm 1 số mẹo nhỏ mà mọi người thường ít để ý.
Viết câu query sử dụng điều kiện trong bảng liên kết
Bạn có một bảng User
và bảng Profile
có liên kết với bảng User
Nếu bạn muốn viết một câu truy vấn lấy ra những Users
có Profile
đã được validated
, thường thì sẽ viết thế này:
# User model
scope :activated, ->{
joins(:profile).where(profiles: { activated: true })
}
Cách này tuy vẫn trả ra kết quả, nhưng nó là hướng tiếp cận sai, logic của Profile
bị lộ bên trong model Users
, điều này vi phạm tính đóng gói của lập trình hướng đối tượng.
Ta nên làm theo cách này:
# Profile model
scope :activated, ->{ where(activated: true) }
# User model
scope :activated, ->{ joins(:profile).merge(Profile.activated) }
Sự khác biệt của nested joins
Hãy cẩn thận với cách bạn sử dụng joins
trong ActiveRecord, ví dụ bảng Users
có mối quan hệ 1 -1 với bảng Profile
, bảng Profile
có mối quan hệ 1 - n với bảng Skill
, mặc định là nó sẽ sử dụng INNER JOIN
nhưng...
User.joins(:profiles).merge(Profile.joins(:skills))
=> SELECT users.* FROM users
INNER JOIN profiles ON profiles.user_id = users.id
LEFT OUTER JOIN skills ON skills.profile_id = profiles.id
Vì vậy nên sử dụng cách này hơn:
User.joins(profiles: :skills)
=> SELECT users.* FROM users
INNER JOIN profiles ON profiles.user_id = users.id
INNER JOIN skills ON skills.profile_id = profiles.id
Exist query
Khi bạn muốn lấy ra những Users mà không có những bài viết nổi tiếng, tức là bạn đang hướng đến câu truy vấn NOT EXISTS
, bạn có thể viết nó vô cùng dễ dàng với những phương thức tiện lợi dưới đây
# Post
scope :famous, ->{ where("view_count > ?", 1_000) }
# User
scope :without_famous_post, ->{
where(_not_exists(Post.where("posts.user_id = users.id").famous))
}
def self._not_exists(scope)
"NOT #{_exists(scope)}"
end
def self._exists(scope)
"EXISTS(#{scope.to_sql})"
end
Bạn có thể làm tương tự với EXISTS
Subqueries
Giả sử bạn cần lấy những bài viết của một tập hợp Users nhất định, nhiều người thường lạm dụng pluck
thế này:
Post.where(user_id: User.created_last_month.pluck(:id))
Lỗi ở đây là nó sẽ thực hiện 2 câu truy vấn: một câu để lấy ra id của Users, một câu để lấy ra những bài viết của users_id đấy. Chúng ta hoàn toàn có thể lấy được kết quả tương tự với một câu query có chứa subquery:
Post.where(user_id: User.created_last_month)
ActiveRecord đã làm tất cả cho bạn.
Quay trở về dạng đơn giản
Đừng quên rằng câu truy vấn ActiveRecord có thể kết hợp với .to_sql
để hiển thị ra một câu lệnh SQL và .explain
để lấy chi tiết,...
Booleans
Tôi đoán bạn kì vọng User.where.not(tall: true)
sẽ tương tự SELECT users.* FROM users WHERE users.tall <> 't'
Nhưng thực tế là nó sẽ trả về Users có tail
được set là false
, còn sẽ không trả về tail
được set là NULL
Sẽ tốt hơn nếu viết thế này:
User.where("users.tall IS NOT TRUE")
hoặc User.where(tall: [false, nil])
Nguồn tham khảo: https://medium.com/@apneadiving/active-records-queries-tricks-2546181a98dd
All rights reserved