Rails: bao gồm vs.


345

Đây không chỉ là câu hỏi "tại sao mọi thứ hoạt động theo cách này" chứ không phải là câu hỏi "Tôi không biết làm thế nào" ...

Vì vậy, phúc âm về việc kéo các bản ghi liên quan mà bạn biết bạn sẽ sử dụng là để sử dụng :includevì bạn sẽ tham gia và tránh một loạt các truy vấn bổ sung:

Post.all(:include => :comments)

Tuy nhiên, khi bạn nhìn vào nhật ký, không có sự tham gia nào xảy ra:

Post Load (3.7ms)   SELECT * FROM "posts"
Comment Load (0.2ms)   SELECT "comments.*" FROM "comments" 
                       WHERE ("comments".post_id IN (1,2,3,4)) 
                       ORDER BY created_at asc) 

đang sử dụng một phím tắt bởi vì nó kéo tất cả các bình luận cùng một lúc, nhưng nó vẫn không phải là một sự tham gia (đó là những gì tất cả các tài liệu dường như nói). Cách duy nhất tôi có thể tham gia là sử dụng :joinsthay vì :include:

Post.all(:joins => :comments)

Và nhật ký hiển thị:

Post Load (6.0ms)  SELECT "posts".* FROM "posts" 
                   INNER JOIN "comments" ON "posts".id = "comments".post_id

Tui bỏ lỡ điều gì vậy? Tôi có một ứng dụng với nửa tá hiệp hội và trên một màn hình tôi hiển thị dữ liệu từ tất cả chúng. Có vẻ như sẽ tốt hơn nếu có một truy vấn tham gia thay vì 6 cá nhân. Tôi biết rằng thông minh về hiệu suất không phải lúc nào cũng tốt hơn khi tham gia thay vì truy vấn riêng lẻ (thực tế nếu bạn dành thời gian, có vẻ như hai truy vấn riêng lẻ ở trên nhanh hơn tham gia), nhưng sau tất cả các tài liệu Tôi đã đọc tôi ngạc nhiên khi thấy :includekhông hoạt động như quảng cáo.

Có lẽ Rails là người nhận thức được vấn đề về hiệu suất và không tham gia trừ một số trường hợp nhất định?


3
nếu bạn đang sử dụng phiên bản cũ hơn của Rails, vui lòng cho biết thông qua các thẻ hoặc trong nội dung câu hỏi của bạn. Mặt khác, nếu bạn đang sử dụng Rails 4 NGAY BÂY GIỜ, thì đó là includes(đối với bất kỳ ai đọc cái này)
onebree

Hiện tại cũng có: tải trước và: eager_load blog.bigbinary.com/2013/07/01/iêu
CJW

Câu trả lời:


179

Dường như :includechức năng đã được thay đổi với Rails 2.1. Rails được sử dụng để tham gia trong mọi trường hợp, nhưng vì lý do hiệu suất, nó đã được thay đổi để sử dụng nhiều truy vấn trong một số trường hợp. Bài đăng trên blog này của Fabio Akita có một số thông tin tốt về thay đổi (xem phần có tên "Đang tải tối ưu hóa háo hức").



Điều này rất hữu ích, cảm ơn. Mặc dù vậy, tôi ước rằng có một cách để buộc Rails thực hiện việc tham gia ngay cả khi không có 'nơi' yêu cầu. Trong một số trường hợp, bạn biết rằng việc tham gia sẽ hiệu quả hơn và sẽ không có nguy cơ trùng lặp.
Jonathan Swartz


@JonathanSwartz Có vẻ như phiên bản mới Rails hỗ trợ điều này bằng cách sử dụng háo hức . Cảm ơn vì liên kết NathanLong
rubyprince

92

.joinssẽ chỉ tham gia các bảng và mang lại các trường đã chọn. nếu bạn gọi các liên kết trên kết quả truy vấn, nó sẽ kích hoạt lại các truy vấn cơ sở dữ liệu

:includessẽ háo hức tải các hiệp hội bao gồm và thêm chúng vào bộ nhớ. :includestải tất cả các thuộc tính bảng bao gồm. Nếu bạn gọi các hiệp hội trên bao gồm kết quả truy vấn, nó sẽ không thực hiện bất kỳ truy vấn nào


71

Sự khác biệt giữa các phép nối và bao gồm là việc sử dụng câu lệnh bao gồm tạo ra một truy vấn SQL lớn hơn nhiều vào bộ nhớ tất cả các thuộc tính từ (các) bảng khác.

Ví dụ: nếu bạn có một bảng đầy đủ các bình luận và bạn sử dụng: joins => người dùng để lấy tất cả thông tin người dùng cho mục đích sắp xếp, v.v ... nó sẽ hoạt động tốt và mất ít thời gian hơn: bao gồm, nhưng nói rằng bạn muốn hiển thị nhận xét cùng với tên người dùng, email, v.v. Để có được thông tin bằng cách sử dụng: tham gia, nó sẽ phải thực hiện các truy vấn SQL riêng cho từng người dùng mà nó tìm nạp, trong khi nếu bạn đã sử dụng: bao gồm thông tin này đã sẵn sàng để sử dụng.

Ví dụ tuyệt vời:

http://railscasts.com/episodes/181-include-vs-joins


55

Gần đây tôi đã đọc thêm về sự khác biệt giữa :joins:includestrong đường ray. Đây là một giải thích về những gì tôi hiểu (với các ví dụ :))

Hãy xem xét kịch bản này:

  • Một người dùng has_many bình luận và một bình luận thuộc về một người dùng.

  • Mô hình Người dùng có các thuộc tính sau: Tên (chuỗi), Tuổi (số nguyên). Mô hình Nhận xét có các thuộc tính sau: Nội dung, user_id. Đối với một bình luận, user_id có thể là null.

Tham gia:

: tham gia thực hiện nối bên trong giữa hai bảng. Như vậy

Comment.joins(:user)

#=> <ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first   comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">, 
     #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,    
     #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">]>

sẽ tìm nạp tất cả các bản ghi trong đó user_id (của bảng nhận xét) bằng user.id (bảng người dùng). Vì vậy, nếu bạn làm

Comment.joins(:user).where("comments.user_id is null")

#=> <ActiveRecord::Relation []>

Bạn sẽ nhận được một mảng trống như hình.

Hơn nữa, các phép nối không tải bảng đã tham gia vào bộ nhớ. Vì vậy, nếu bạn làm

comment_1 = Comment.joins(:user).first

comment_1.user.age
#=>←[1m←[36mUser Load (0.0ms)←[0m  ←[1mSELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1←[0m  [["id", 1]]
#=> 24

Như bạn thấy, comment_1.user.agesẽ kích hoạt lại một truy vấn cơ sở dữ liệu trong nền để nhận kết quả

Bao gồm:

: bao gồm thực hiện nối ngoài trái giữa hai bảng. Như vậy

Comment.includes(:user)

#=><ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">,
   #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,
   #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">,    
   #<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

sẽ dẫn đến một bảng tham gia với tất cả các hồ sơ từ bảng ý kiến. Vì vậy, nếu bạn làm

Comment.includes(:user).where("comment.user_id is null")
#=> #<ActiveRecord::Relation [#<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

nó sẽ tìm nạp các bản ghi trong đó bình luận.user_id là không như hiển thị.

Hơn nữa bao gồm tải cả hai bảng trong bộ nhớ. Vì vậy, nếu bạn làm

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Như bạn có thể nhận thấy comment_1.user.age chỉ cần tải kết quả từ bộ nhớ mà không cần thực hiện truy vấn cơ sở dữ liệu trong nền.


Đây có phải là cho Rails 4?
onebree

@HunterStevens: Đúng vậy
Aaditi Jain

54

Ngoài việc xem xét hiệu suất, còn có một sự khác biệt về chức năng. Khi bạn tham gia bình luận, bạn đang yêu cầu các bài đăng có bình luận - một sự tham gia bên trong theo mặc định. Khi bạn bao gồm các ý kiến, bạn đang yêu cầu tất cả các bài đăng - tham gia bên ngoài.


10

tl; dr

Tôi đối chiếu chúng theo hai cách:

tham gia - Đối với lựa chọn có điều kiện của hồ sơ.

bao gồm - Khi sử dụng liên kết trên mỗi thành viên của tập kết quả.

Phiên bản dài hơn

Tham gia có nghĩa là để lọc tập kết quả đến từ cơ sở dữ liệu. Bạn sử dụng nó để thực hiện các thao tác trên bàn của bạn. Hãy nghĩ về điều này như một mệnh đề where thực hiện lý thuyết tập hợp.

Post.joins(:comments)

giống như

Post.where('id in (select post_id from comments)')

Ngoại trừ rằng nếu có nhiều hơn một bình luận, bạn sẽ nhận được các bài đăng trùng lặp trở lại với các liên kết. Nhưng mỗi bài viết sẽ là một bài viết có ý kiến. Bạn có thể sửa lỗi này với sự khác biệt:

Post.joins(:comments).count
=> 10
Post.joins(:comments).distinct.count
=> 2

Trong hợp đồng, includesphương thức sẽ chỉ đảm bảo rằng không có truy vấn cơ sở dữ liệu bổ sung nào khi tham chiếu mối quan hệ (để chúng tôi không thực hiện truy vấn n + 1)

Post.includes(:comments).count
=> 4 # includes posts without comments so the count might be higher.

Đạo đức là, sử dụng joinskhi bạn muốn thực hiện các thao tác thiết lập có điều kiện và sử dụng includeskhi bạn sẽ sử dụng một mối quan hệ trên mỗi thành viên của một bộ sưu tập.


Điều đó distinctnhận được tôi mọi lúc. Cảm ơn bạn!
Ben Hull

4

.joins hoạt động như tham gia cơ sở dữ liệu và nó tham gia hai hoặc nhiều bảng và tìm nạp dữ liệu được chọn từ phụ trợ (cơ sở dữ liệu).

.có hoạt động như tham gia trái của cơ sở dữ liệu. Nó tải tất cả các bản ghi của bên trái, không có liên quan của mô hình bên phải. Nó được sử dụng để háo hức tải vì nó tải tất cả các đối tượng liên quan trong bộ nhớ. Nếu chúng ta gọi các liên kết trên bao gồm kết quả truy vấn thì nó không thực hiện truy vấn trên cơ sở dữ liệu, Nó chỉ trả về dữ liệu từ bộ nhớ vì nó đã tải dữ liệu trong bộ nhớ.


0

'Tham gia' chỉ được sử dụng để tham gia các bảng và khi bạn gọi các liên kết trên các liên kết thì nó sẽ lại truy vấn (có nghĩa là nhiều truy vấn sẽ kích hoạt)

lets suppose you have tow model, User and Organisation
User has_many organisations
suppose you have 10 organisation for a user 
@records= User.joins(:organisations).where("organisations.user_id = 1")
QUERY will be 
 select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1

it will return all records of organisation related to user
and @records.map{|u|u.organisation.name}
it run QUERY like 
select * from organisations where organisations.id = x then time(hwo many organisation you have)

tổng số SQL là 11 trong trường hợp này

Nhưng với 'bao gồm' sẽ háo hức tải các liên kết được bao gồm và thêm chúng vào bộ nhớ (tải tất cả các liên kết trong lần tải đầu tiên) và không truy vấn lại

khi bạn nhận được các bản ghi với bao gồm like @ records = User.includes (: tổ chức) .where ("organisations.user_id = 1") thì truy vấn sẽ là

select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
and 


 select * from organisations where organisations.id IN(IDS of organisation(1, to 10)) if 10 organisation
and when you run this 

@ records.map {| u | u. Organisation.name} không có truy vấn nào sẽ kích hoạt

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.