Một số mẹo viết câu truy vấn hiệu quả
Chỉ lấy dữ liệu cần Active record cung cấp interface để tương tác với dữ liệu. Rất dễ dàng tạo mối quan hệ giữa các model và lấy bản ghi, như dưới đây def get_last_company User.find(1).companies.last end Câu lệnh trên sẽ lấy company cuối cùng user đầu tiên. Rất đơn giản! Nhưng bạn đã bao ...
Chỉ lấy dữ liệu cần
Active record cung cấp interface để tương tác với dữ liệu. Rất dễ dàng tạo mối quan hệ giữa các model và lấy bản ghi, như dưới đây
def get_last_company User.find(1).companies.last end
Câu lệnh trên sẽ lấy company cuối cùng user đầu tiên. Rất đơn giản! Nhưng bạn đã bao giờ tự hỏi liệu có cần thiết phải lấy toàn bộ các trường của đối tượng User chỉ để lấy company có liên quan đến user? Chắc chắn là không. Một User có nhiều Companies liên kết với nó, tuy nhiên chỉ cần id của User để lấy được Company được liên kết với nó. Việc lưu trữ các đối tượng trong Ruby cũng tốn bộ nhớ. Ban đầu khi bảng User chỉ có ít trường thì khó mà nhìn thấy sự khác biệt, nhưng khi bảng User phát triển, được thêm nhiều trường, bạn sẽ cảm thấy may mắn vì đã để tâm đến nó ngay từ đầu. Cách cải thiện sẽ là:
def get_last_company User.select(:id).find(1).companies.last end
Một truy vấn kiểu select user.id, user.some_other_required_field from users... luôn tốt hơn select * from users...
Select hay pluck?
select lấy các trường được chọn và trả về một đối tượng model mà bạn có thể gọi đến các hàm trong model đó hoặc các model liên quan. pluck cũng lấy các trường được chọn, nhưng nó chỉ trả về một mảng các giá trị và tốn ít bộ nhớ hơn. Ví dụ, User.select(:id, :company_id) sẽ trả về một đối tượng User cùng với id và copany_id để bạn có thể gọi đến các phương thức như companies. Nhưng nếu bạn dùng User.pluck(:id, :company_id), nó sẽ trả về kết quả lỗi.
Dùng phương thức của Ruby hay phương thức truy vấn?
Một thói quen đơn giản là gọi đến các phương thức của Ruby thay vì phương thức truy vấn có thể làm giảm số lần gọi đến cơ sở dữ liệu không cần thiết. Để đếm tổng số người bạn của một User ta dùng current_user.friends.count, nó sẽ thực thi một câu truy vấn đếm. Liệu có cần thiết phải thực hiện câu truy vấn này sau khi đã lấy tất cả friends? Nếu tất cả friends đã được load, chỉ cần dùng current_user.friends.lenght, cách này sẽ giúp tiết kiệm một lần gọi vào cơ sở dữ liệu. Sự khác biêt là length là phương thức của Ruby, trả về tổng số đối tượng, còn count là một câu lệnh truy vấn select count(*) from table. Hãy cố gắng dùng phương thức của Ruby bất cứ lúc nào có thể để giảm số lần tương tác với cơ sở dữ liệu.
Sử dụng IN
Một trường hợp khá phổ biến là khi muốn truy vấn dữ liệu từ một trường có giá trị được định nghĩa trong enum
class Person < ActiveRecord::Base enum preferred_food: [:pizza, :pasta, :noodles, :burgers] end
Như trong ví dụ trên, để tìm những người thích pasta hoặc noodles, ta viết:
class Person < ActiveRecord::Base enum preferred_food: [:pizza, :pasta, :noodles, :burgers] def self.preferres_pasta_or_noodles where(preferred_food: [Person.preferred_foods[:pasta], Person.preferred_foods[:noodles] ]) # hoặc viết # where('preferred_food IN ( ? ) ', [1, 2]) end end
Tiết kiệm thời gian với Transaction
Để cập nhật nhiều bản ghi với cùng một dữ liệu, ví dụ như chuyển giá trị Boolean sang false, ta viết:
User.where(is_admin: true).update_all(is_admin: false)
Kết quả của nó chỉ là 1 câu truy vấn. Nhưng nếu cập nhật nhiều bản ghi với dữ liệu khác nhau thì một câu truy vấn là không thể, nhưng ta có thể viết nó trong một Transaction để tăng hiệu suất. Cách làm này sẽ không giúp cập nhật tất cả bản ghi trong cùng một lần, nó vẫn sẽ thực hiện từng đấy câu truy vấn, nhưng chỉ commit transaction đúng một lần. Để dễ hiểu, đoạn code dưới đây để update title của nhiều bài post mà không đặt trong transaction:
20.times do |i| Post.update(title: "UPDATE_TITLE#{i}") end
Đây là cách các câu truy vấn chạy: Nhưng khi để trong 1 transaction:
ActiveRecord::Base.transaction do 20.times do |i| Post.update(title: "UPDATE_TITLE#{i}") end end
Các câu truy vấn sẽ có dạng thế này:
Đừng sợ dùng joins
Tại sao lại dùng joins và khi nào thì nên dùng nó? Sẽ rất đơn giản sau khi bạn hiểu được sự khác biệt giữa các loại joins. Thông thường, chúng ta hay sử dụng inner join và left outer join. User.joins(:post) sẽ cho phép bạn tìm kiếm những user có bài post với tiêu đề nào đó:
User.joins(:posts).where('posts.title = ? ', "SOME TITLE")
joins rất hữu dụng trong nhiều trường hợp và có thể giúp bạn chỉ lấy dữ liệu bạn muốn, giống như trong ví dụ trên. Vì vậy, bất cứ khi nào bạn cần lọc dữ liệu dựa trên bản ghi liên quan, đấy chính là lúc dùng joins. Bạn có thể đọc thêm về joins ở đây.
Dùng includes
Giả sử bạn cần lấy tiêu đề của tất cả bài viết của 10 tác giả đầu tiên. Bạn sẽ nhanh chóng viết:
authors = Author.first(10) authors.each do |author| author.posts.pluck(:title) end
Tốt, chỉ lấy tiêu đề, nhưng một câu truy vấn để lấy tất cả tác giả, một câu truy vấn để lấy bài viết cho mỗi tác giả, tổng cộng là 11 câu truy vấn. Có cách khác để giải quyết chỉ với 2 câu lệnh
authors = Author.first(10).includes(:posts) authors.each do |author| author.posts.pluck(:title) end
Chỉ một chút thay đổi, thêm includes(:post) vào câu truy vấn và vấn đề đã được giải quyết.
Nguồn tham khảo: https://blog.codeship.com/writing-efficient-queries/