Một số mẹo viết câu truy vấn hiệu quả (tiếp)
Ở 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 ...
Ở 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