Khi nào includes làm một join, và khi nào nó làm một truy vấn thứ hai
Một ngày nào đó, chúng ta làm việc cho ứng dụng Rails của chúng ta và chúng ta gặp phải vấn đề khi một truy vấn ActiveRecord với một mệnh đề includes đã thực hiện join, chúng ta cảm thấy nó sẽ hiệu quả hơn và cải thiện hiệu suất để thực hiện truy vấn thứ hai. Chúng ta đã có nghĩ rằng ActiveRecord ...
Một ngày nào đó, chúng ta làm việc cho ứng dụng Rails của chúng ta và chúng ta gặp phải vấn đề khi một truy vấn ActiveRecord với một mệnh đề includes đã thực hiện join, chúng ta cảm thấy nó sẽ hiệu quả hơn và cải thiện hiệu suất để thực hiện truy vấn thứ hai. Chúng ta đã có nghĩ rằng ActiveRecord sẽ làm việc này, và chắc chắn là sẽ phỏng đoán đây là một sự lựa chọn thông minh. Hãy khai thác để xem khi sử dụng includes thực hiện một truy vấn thứ hai, khi nó thực hiện một join, và tại sao chúng ta đã ngớ ngẩn để nghĩ rằng chúng ta đã thông minh hơn ActiveRecord.
Chúng ta sẽ sử dụng hai mô hình này, với một cặp đơn giản has_many và belong_to:
class User < ActiveRecord::Base has_many :cards end class Card < ActiveRecord::Base belongs_to :user def self.with_phrase(phrase) where(phrase: phrase) end end
Nếu chúng ta chỉ đơn giản includes (: cards) từ mối quan hệ user, chúng ta nhận được hai truy vấn:
User.where(id: 1).includes(:cards)
SELECT "users".* FROM "users" WHERE "users"."id" = 1 SELECT "cards".* FROM "cards" WHERE "cards"."user_id" IN (1)
Nếu chúng ta join vào bảng, thì đó là một truy vấn với một INNER JOIN:
User.where(id: 1).joins(:cards).includes(:cards)
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."created_at" AS t0_r2, "users"."updated_at" AS t0_r3, "cards"."id" AS t1_r0, "cards"."user_id" AS t1_r1, "cards"."phrase" AS t1_r2, "cards"."created_at" AS t1_r3, "cards"."updated_at" AS t1_r4 FROM "users" INNER JOIN "cards" ON "cards"."user_id" = "users"."id" WHERE "users"."id" = 1
Điều này có ý nghĩa - chúng ta không còn chỉ muốn tải cards, nhưng bây giờ chúng ta chỉ tải user có card (và chúng ta cũng đang tải card). ActiveRecord cho chúng ta tầm quan trọng của user và sự tải mong muốn thông qua một truy vấn SQL, và điều đó khá gọn gàng. Nếu chúng ta tham chiếu bảng, sau đó chúng ta có được một truy vấn - với LEFT OUTER JOIN thay vì INNER JOIN. Điều này cho ActiveRecord rằng chúng ta sẽ được tham chiếu bảng đó trong truy vấn của chúng tôi. Nếu chúng ta không nói cho ActiveRecord và cố gắng tham chiếu bảng đó thì 1 vài điều tồi tệ xảy ra:
User.includes(:cards).merge(Card.with_phrase("banana")) PG::UndefinedTable: ERROR: missing FROM-clause entry for table "cards" LINE 1: SELECT "users".* FROM "users" WHERE "cards"."phrase" = $1 ^
Dưới đây là ví dụ về việc sử dụng tham chiếu chính xác. Bây giờ SQL được tạo ra có thể phạm vi dữ liệu trong bảng thẻ đúng cách.
User.includes(:cards).references(:cards).merge(Card.with_phrase("hammocks"))
SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."created_at" AS t0_r2, "users"."updated_at" AS t0_r3, "cards"."id" AS t1_r0, "cards"."user_id" AS t1_r1, "cards"."phrase" AS t1_r2, "cards"."created_at" AS t1_r3, "cards"."updated_at" AS t1_r4 FROM "users" LEFT OUTER JOIN "cards" ON "cards"."user_id" = "users"."id" WHERE "cards"."phrase" = "hammocks"
Vì vậy, trở lại với vấn đề chúng ta đã trải qua. Chúng ta đã join từ một bảng, thông qua một vài, vào một bảng có số lượng lớn bảng ghi. Truy vấn này không thể chấp nhận được. Chúng ta đã tìm kiếm và không thành công trong việc tìm kiếm cơ sở dữ liệu-tối ưu hóa, vì vậy chúng ta đã đi với chỉ cần phá vỡ các truy vấn thành từng phần. Ví dụ: hãy sửa đổi kiến trúc ví dụ của chúng ta và giới thiệu một bảng join:
class User < ActiveRecord::Base has_many :hands has_many :cards, through: :hands end class Hand < ActiveRecord::Base belongs_to :user belongs_to :card def self.with_cards(cards) where(card_id: cards) end end class Card < ActiveRecord::Base def self.with_phrase(phrase) where(phrase: phrase) end end
Bây giờ nếu chúng ta chỉ có 5 uers, nhưng chúng ta có 10 tỷ cards, trước tiên chúng ta có thể muốn mở rộng phạm vi cho các cards có liên quan, sau đó lấy user và chỉ join bảng Hand - thay vì đi qua bảng Card. Chúng ta có thể làm điều này với hai truy vấn:
cards = Card.with_phrase('hammocks') users = User.joins(:hands).merge(Hand.with_cards(cards))
Điều này đơn giản hóa SQL cho mỗi truy vấn và có thể nhanh hơn.
Cảm ơn bạn đã đọc bài! nguồn : https://www.foraker.com/blog/active-record-includes-query-logic