Rails: include vs. : joins


345

นี่เป็นคำถามที่ว่า "ทำไมสิ่งต่าง ๆ ถึงทำงานแบบนี้" มากกว่าคำถามที่ว่า "ฉันไม่รู้จะทำอย่างไร"

ดังนั้นพระกิตติคุณในการดึงบันทึกที่เกี่ยวข้องที่คุณรู้ว่าคุณจะใช้คือการใช้:includeเพราะคุณจะได้เข้าร่วมและหลีกเลี่ยงการสืบค้นเพิ่มเติม:

Post.all(:include => :comments)

อย่างไรก็ตามเมื่อคุณดูล็อกจะไม่มีการเข้าร่วมเกิดขึ้น:

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) 

มันเป็นทางลัดเพราะมันดึงทุกความคิดเห็นในครั้งเดียว แต่ก็ยังไม่เข้าร่วม (ซึ่งเป็นสิ่งเอกสารทั้งหมดที่ดูเหมือนว่าจะพูด) วิธีเดียวที่ฉันจะเข้าร่วมได้คือใช้:joinsแทน:include:

Post.all(:joins => :comments)

และบันทึกแสดง:

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

ฉันพลาดอะไรไปรึเปล่า? ฉันมีแอพที่มีความสัมพันธ์ครึ่งโหลและบนหน้าจอเดียวฉันแสดงข้อมูลจากพวกเขาทั้งหมด ดูเหมือนว่าจะเป็นการดีกว่าถ้ามีแบบสอบถามแบบใช้ร่วมกันแทนที่จะเป็น 6 คน ฉันรู้ว่าการทำงานที่ชาญฉลาดนั้นไม่ได้ดีไปกว่าการเข้าร่วมมากกว่าการสืบค้นแต่ละครั้ง (อันที่จริงถ้าคุณใช้เวลาตามเวลาดูเหมือนว่าข้อความค้นหาสองรายการข้างต้นจะเร็วกว่าการเข้าร่วม) แต่หลังจากเอกสารทั้งหมด ฉันอ่านมาแล้วฉันประหลาดใจที่เห็น:includeว่าไม่ได้ทำงานตามที่โฆษณาไว้

บางที Rails เป็นรู้ทันปัญหาประสิทธิภาพการทำงานและไม่ได้เข้าร่วมยกเว้นในบางกรณี?


3
หากคุณใช้ Rails รุ่นเก่ากว่าโปรดระบุว่าผ่านทางแท็กหรือในส่วนคำถามของคุณ มิฉะนั้นถ้าคุณใช้ Rails 4 ตอนนี้ก็เป็นincludes(สำหรับทุกคนที่อ่านข้อความนี้)
onebree

นอกจากนี้ยังมีอยู่ในขณะนี้: eager_load: พรีโหลดและblog.bigbinary.com/2013/07/01/...
CJW

คำตอบ:


179

ดูเหมือนว่า:includeฟังก์ชั่นนี้เปลี่ยนไปด้วย Rails 2.1 Rails เคยทำการเข้าร่วมในทุกกรณี แต่ด้วยเหตุผลด้านประสิทธิภาพมันถูกเปลี่ยนไปใช้หลาย ๆ แบบสอบถามในบางสถานการณ์ โพสต์บล็อกนี้โดย Fabio Akita มีข้อมูลที่ดีเกี่ยวกับการเปลี่ยนแปลง (ดูในส่วนชื่อ "Optimized Eager Loading")



สิ่งนี้มีประโยชน์มากขอบคุณ ฉันหวังว่าจะมีวิธีบังคับให้ Rails เข้าร่วมได้โดยไม่ต้องใช้ 'ที่ไหน' ที่ต้องใช้ ในบางกรณีคุณรู้ว่าการเข้าร่วมจะมีประสิทธิภาพมากขึ้นและจะไม่เกิดความเสี่ยงในการทำซ้ำ
Jonathan Swartz

1
ดูเพิ่มเติมที่: blog.bigbinary.com/2013/07/01/…
นาธานลอง

@JonathanSwartz ดูเหมือนว่าทางรถไฟรุ่นใหม่รองรับการใช้eagerload ขอบคุณสำหรับลิงค์ NathanLong
rubyprince

92

.joinsจะเข้าร่วมตารางและนำเขตข้อมูลที่เลือกกลับมา ถ้าคุณเรียกการเชื่อมโยงกับผลการสืบค้นแบบสอบถามมันจะทำการสืบค้นฐานข้อมูลอีกครั้ง

:includesจะกระตือรือร้นที่จะโหลดการเชื่อมโยงที่รวมไว้และเพิ่มเข้าไปในหน่วยความจำ :includesโหลดแอตทริบิวต์ตารางรวมทั้งหมด หากคุณเรียกการเชื่อมโยงกับผลการสืบค้นรวมจะไม่เริ่มการสืบค้นใด ๆ


71

ความแตกต่างระหว่างการรวมและการรวมคือการใช้คำสั่ง include เพื่อสร้างเคียวรี SQL ที่ใหญ่กว่ามากโหลดลงในหน่วยความจำแอ็ตทริบิวต์ทั้งหมดจากตารางอื่น

ตัวอย่างเช่นหากคุณมีตารางที่เต็มไปด้วยความคิดเห็นและคุณใช้ a: joins => ผู้ใช้เพื่อดึงข้อมูลผู้ใช้ทั้งหมดสำหรับการเรียงลำดับ ฯลฯ มันจะทำงานได้ดีและใช้เวลาน้อยกว่า: รวม แต่บอกว่าคุณต้องการแสดง ความคิดเห็นพร้อมกับชื่อผู้ใช้อีเมลและอื่น ๆ ในการรับข้อมูลโดยใช้: รวมมันจะต้องแยกแบบสอบถาม SQL สำหรับผู้ใช้แต่ละคนที่ดึงมาในขณะที่ถ้าคุณใช้: รวมข้อมูลนี้พร้อมใช้งานแล้ว

ตัวอย่างที่ดี:

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


55

ฉันเพิ่งอ่านเพิ่มเติมเกี่ยวกับความแตกต่างระหว่าง:joinsและ:includesในราง นี่คือคำอธิบายของสิ่งที่ฉันเข้าใจ (พร้อมตัวอย่าง :))

พิจารณาสถานการณ์นี้:

  • ผู้ใช้มีความคิดเห็นจำนวนมากและความคิดเห็นเป็นของผู้ใช้

  • โมเดลผู้ใช้มีคุณสมบัติดังต่อไปนี้: ชื่อ (สตริง), อายุ (จำนวนเต็ม) โมเดลความคิดเห็นมีคุณสมบัติดังต่อไปนี้: เนื้อหา, user_id สำหรับความคิดเห็น user_id สามารถเป็นโมฆะ

เข้าร่วม:

: joins ทำการรวมภายในระหว่างสองตาราง ดังนั้น

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">]>

จะดึงข้อมูลระเบียนทั้งหมดที่ user_id (จากตารางความคิดเห็น) เท่ากับ user.id (ตารางผู้ใช้) ดังนั้นถ้าคุณทำ

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

#=> <ActiveRecord::Relation []>

คุณจะได้อาร์เรย์ที่ว่างเปล่าดังที่แสดง

ยิ่งกว่านั้นการรวมไม่โหลดตารางการรวมในหน่วยความจำ ดังนั้นถ้าคุณทำ

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

อย่างที่คุณเห็นcomment_1.user.ageจะเริ่มการสืบค้นฐานข้อมูลอีกครั้งในพื้นหลังเพื่อรับผลลัพธ์

รวม:

: include ทำการรวมภายนอกด้านซ้ายระหว่างสองตาราง ดังนั้น

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">]>

จะส่งผลให้ตารางที่เข้าร่วมพร้อมระเบียนทั้งหมดจากตารางความคิดเห็น ดังนั้นถ้าคุณทำ

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">]>

มันจะดึงข้อมูลระเบียนที่ comments.user_id เป็นศูนย์ดังที่แสดง

นอกจากนี้ยังมีการโหลดทั้งตารางในหน่วยความจำ ดังนั้นถ้าคุณทำ

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

ในขณะที่คุณสามารถสังเกตเห็น comment_1.user.age เพียงแค่โหลดผลลัพธ์จากหน่วยความจำโดยไม่ต้องค้นหาคิวรีฐานข้อมูลในพื้นหลัง


นี่สำหรับ Rails 4 ไหม?
onebree

@HunterStevens: ใช่มันเป็น
Aaditi Jain

54

นอกเหนือจากข้อควรพิจารณาด้านประสิทธิภาพแล้วยังมีความแตกต่างในด้านการใช้งานอีกด้วย เมื่อคุณเข้าร่วมแสดงความคิดเห็นคุณจะขอโพสต์ที่มีความคิดเห็น - การเข้าร่วมภายในโดยค่าเริ่มต้น เมื่อคุณรวมความคิดเห็นคุณจะขอโพสต์ทั้งหมด - การเข้าร่วมภายนอก


10

TL; DR

ฉันเปรียบเทียบมันในสองวิธี:

ร่วม - สำหรับการเลือกเรคคอร์ดอย่างมีเงื่อนไข

รวมถึง - เมื่อใช้การเชื่อมโยงกับสมาชิกแต่ละคนของชุดผลลัพธ์

รุ่นที่ยาวกว่า

ตัวเชื่อมหมายถึงการกรองชุดผลลัพธ์ที่มาจากฐานข้อมูล คุณใช้มันเพื่อตั้งค่าการดำเนินการบนโต๊ะของคุณ คิดว่านี่เป็นประโยคที่ทำทฤษฎีเซต

Post.joins(:comments)

เป็นเช่นเดียวกับ

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

ยกเว้นว่าถ้ามีมากกว่าหนึ่งความคิดเห็นคุณจะได้รับโพสต์ซ้ำกับการรวม แต่ทุกโพสต์จะเป็นโพสต์ที่มีความคิดเห็น คุณสามารถแก้ไขสิ่งนี้ได้อย่างชัดเจน:

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

ในสัญญาincludesวิธีการนั้นจะทำให้แน่ใจได้ว่าไม่มีการสืบค้นฐานข้อมูลเพิ่มเติมเมื่ออ้างอิงถึงความสัมพันธ์ (เพื่อที่เราจะไม่ทำการค้นหา n + 1)

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

คุณธรรมคือใช้joinsเมื่อคุณต้องการทำชุดปฏิบัติการตามเงื่อนไขและใช้includesเมื่อคุณจะใช้ความสัมพันธ์กับสมาชิกแต่ละคนของคอลเลกชัน


นั่นdistinctทำให้ฉันทุกครั้ง ขอบคุณ!
เบ็นฮัลล์

4

.joins ทำงานเป็นฐานข้อมูลเข้าร่วมและเข้าร่วมสองตารางหรือมากกว่าและดึงข้อมูลที่เลือกจากแบ็กเอนด์ (ฐานข้อมูล)

. รวมการทำงานเป็นซ้ายเข้าร่วมของฐานข้อมูล มันโหลดบันทึกทั้งหมดของด้านซ้ายไม่มีความเกี่ยวข้องของรูปแบบด้านขวา มันถูกใช้เพื่อโหลดกระตือรือร้นเพราะมันโหลดวัตถุที่เกี่ยวข้องทั้งหมดในหน่วยความจำ ถ้าเราเรียกการเชื่อมโยงกับผลการสืบค้นรวมแล้วมันไม่ได้ทำการสืบค้นในฐานข้อมูลมันเพียงแค่ส่งคืนข้อมูลจากหน่วยความจำเพราะมันได้โหลดข้อมูลในหน่วยความจำแล้ว


0

'joins' เพิ่งใช้เพื่อเข้าร่วมตารางและเมื่อคุณเรียกการเชื่อมโยงกับการรวมแล้วจะทำการสอบถามอีกครั้ง (หมายถึงแบบสอบถามจำนวนมากจะเริ่มทำงาน)

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)

จำนวน SQL ทั้งหมดคือ 11 ในกรณีนี้

แต่ด้วย 'รวมถึง' จะกระตือรือร้นที่จะโหลดการเชื่อมโยงที่รวมไว้และเพิ่มเข้าไปในหน่วยความจำ (โหลดการเชื่อมโยงทั้งหมดในการโหลดครั้งแรก) และจะไม่เริ่มการสืบค้นอีก

เมื่อคุณได้รับบันทึกที่มี include เช่น @ records = User.includes (: Organisations) .where ("organisations.user_id = 1") แบบสอบถามจะเป็น

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} ไม่มีการสอบถามใด ๆ ที่จะดำเนินการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.