Rails: termasuk vs.: bergabung

345

Ini lebih dari pertanyaan "mengapa hal-hal bekerja seperti ini" daripada pertanyaan "Saya tidak tahu bagaimana melakukan ini" ...

Jadi, Injil menarik catatan terkait yang Anda tahu akan Anda gunakan adalah menggunakan :includekarena Anda akan bergabung dan menghindari sejumlah pertanyaan tambahan:

Post.all(:include => :comments)

Namun ketika Anda melihat log, tidak ada gabungan yang terjadi:

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) 

Hal ini mengambil jalan pintas karena menarik semua komentar sekaligus, tapi masih belum join (yang adalah apa yang semua dokumentasi sepertinya mengatakan). Satu-satunya cara saya dapat bergabung adalah dengan menggunakan :joinsalih-alih :include:

Post.all(:joins => :comments)

Dan log menunjukkan:

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

Apakah saya melewatkan sesuatu? Saya memiliki aplikasi dengan setengah lusin asosiasi dan pada satu layar saya menampilkan data dari semuanya. Sepertinya akan lebih baik untuk memiliki satu permintaan join-ed daripada 6 orang. Saya tahu bahwa menurut kinerja, tidak selalu lebih baik untuk melakukan penggabungan daripada kueri individual (bahkan jika Anda menghabiskan waktu, sepertinya dua kueri individual di atas lebih cepat daripada gabung), tetapi setelah semua dokumen Saya telah membaca saya terkejut melihat :includetidak berfungsi seperti yang diiklankan.

Mungkin Rails adalah sadar akan masalah kinerja dan tidak bergabung kecuali dalam kasus-kasus tertentu?

Rob Cameron
sumber
3
jika Anda menggunakan versi Rails yang lebih lama, sebutkan itu melalui tag atau di badan pertanyaan Anda. Kalau tidak, jika Anda menggunakan Rails 4 SEKARANG, itu includes(untuk siapa pun yang membaca ini)
onebree
Juga ada sekarang: preload dan: eager_load blog.bigbinary.com/2013/07/01/…
CJW

Jawaban:

179

Tampaknya :includefungsionalitas diubah dengan Rails 2.1. Rails digunakan untuk melakukan join dalam semua kasus, tetapi untuk alasan kinerja itu diubah untuk menggunakan beberapa kueri dalam beberapa keadaan. Posting blog ini oleh Fabio Akita memiliki beberapa informasi bagus tentang perubahan tersebut (lihat bagian yang berjudul "Dioptimalkan Eager Memuat").

Greg Campbell
sumber
Ini sangat membantu, terima kasih. Saya berharap bahwa ada cara untuk memaksa Rails untuk bergabung bahkan tanpa 'di mana' yang mengharuskannya. Dalam beberapa kasus, Anda tahu bergabung akan lebih efisien dan tidak akan menimbulkan risiko duplikasi.
Jonathan Swartz
1
Lihat juga: blog.bigbinary.com/2013/07/01/...
Nathan Long
@ JonathanSwartz Sepertinya Rails versi baru mendukung ini menggunakan eagerload . Terima kasih untuk tautan NathanLong
rubyprince
92

.joinshanya akan bergabung dengan tabel dan membawa bidang yang dipilih sebagai imbalan. jika Anda memanggil asosiasi pada hasil permintaan bergabung, itu akan memunculkan query database lagi

:includesakan dengan bersemangat memuat asosiasi yang disertakan dan menambahkannya dalam memori. :includesmemuat semua atribut tabel yang disertakan. Jika Anda memanggil asosiasi di hasil sertakan kueri, itu tidak akan memunculkan pertanyaan

Prem
sumber
71

Perbedaan antara gabungan dan sertakan adalah bahwa dengan menggunakan pernyataan sertakan menghasilkan kueri SQL yang jauh lebih besar memuat ke dalam semua atribut dari tabel lainnya.

Misalnya, jika Anda memiliki tabel yang penuh dengan komentar dan Anda menggunakan: joins => pengguna untuk menarik semua informasi pengguna untuk keperluan penyortiran, dll. Itu akan berfungsi dengan baik dan membutuhkan waktu lebih sedikit dari: sertakan, tetapi katakan Anda ingin menampilkan komentar bersama dengan nama pengguna, email, dll. Untuk mendapatkan informasi menggunakan: bergabung, itu harus membuat pertanyaan SQL terpisah untuk setiap pengguna yang dijemput, sedangkan jika Anda menggunakan: sertakan informasi ini siap digunakan.

Contoh yang bagus:

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

Holden
sumber
55

Saya baru saja membaca lebih lanjut tentang perbedaan antara :joinsdan:includes di rel. Berikut ini adalah penjelasan dari apa yang saya mengerti (dengan contoh :))

Pertimbangkan skenario ini:

  • Pengguna memiliki banyak komentar dan komentar menjadi milik Pengguna.

  • Model Pengguna memiliki atribut berikut: Nama (string), Usia (integer). Model Komentar memiliki atribut berikut: Konten, user_id. Untuk komentar, user_id bisa menjadi nol.

Bergabung:

: joins melakukan gabungan internal antara dua tabel. Jadi

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

akan mengambil semua catatan di mana user_id (dari tabel komentar) sama dengan user.id (tabel pengguna). Jadi, jika Anda melakukannya

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

#=> <ActiveRecord::Relation []>

Anda akan mendapatkan array kosong seperti yang ditunjukkan.

Selain itu bergabung tidak memuat tabel yang bergabung dalam memori. Jadi, jika Anda melakukannya

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

Seperti yang Anda lihat, comment_1.user.ageakan menjalankan kueri basis data lagi di latar belakang untuk mendapatkan hasil

Termasuk:

: termasuk melakukan gabungan luar kiri antara dua tabel. Jadi

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

akan menghasilkan tabel bergabung dengan semua catatan dari tabel komentar. Jadi, jika Anda melakukannya

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

itu akan mengambil catatan di mana comments.user_id tidak ada seperti yang ditunjukkan.

Apalagi termasuk memuat kedua tabel dalam memori. Jadi, jika Anda melakukannya

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Karena Anda dapat melihat comment_1.user.age cukup memuat hasil dari memori tanpa melepaskan kueri basis data di latar belakang.

Aaditi Jain
sumber
Apakah ini untuk Rails 4?
onebree
@HunterStevens: Ya itu
Aaditi Jain
54

Selain pertimbangan kinerja, ada perbedaan fungsional juga. Ketika Anda bergabung dengan komentar, Anda meminta posting yang memiliki komentar - gabungan internal secara default. Ketika Anda memasukkan komentar, Anda meminta semua posting - gabungan luar.

Brian Maltzan
sumber
10

tl; dr

Saya membandingkannya dengan dua cara:

joins - Untuk pemilihan rekaman bersyarat.

termasuk - Saat menggunakan asosiasi pada setiap anggota set hasil.

Versi yang lebih panjang

Bergabung dimaksudkan untuk memfilter set hasil yang berasal dari database. Anda menggunakannya untuk melakukan operasi yang ditetapkan di meja Anda. Pikirkan ini sebagai klausa mana yang melakukan teori himpunan.

Post.joins(:comments)

sama dengan

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

Kecuali bahwa jika ada lebih dari satu komentar, Anda akan mendapatkan duplikat kembali dengan bergabung. Tetapi setiap posting akan menjadi posting yang memiliki komentar. Anda dapat memperbaikinya dengan berbeda:

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

Dalam kontrak, includesmetode ini hanya akan memastikan bahwa tidak ada kueri basis data tambahan saat mereferensikan relasi (sehingga kami tidak membuat n + 1 kueri)

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

Moralnya adalah, gunakan joinsketika Anda ingin melakukan operasi pengaturan bersyarat dan gunakan includesketika Anda akan menggunakan relasi pada setiap anggota koleksi.

Kevin Choubacha
sumber
Itu distinctmembuat saya setiap waktu. Terima kasih!
Ben Hull
4

.gabung berfungsi sebagai basis data bergabung dan itu menggabungkan dua atau lebih tabel dan mengambil data yang dipilih dari backend (database).

.termasuk kerja sebagai gabung kiri dari basis data. Itu memuat semua catatan sisi kiri, tidak memiliki relevansi model sisi kanan. Ini digunakan untuk mempercepat pemuatan karena memuat semua objek terkait dalam memori. Jika kita memanggil asosiasi pada hasil memasukkan kueri maka itu tidak memecat kueri pada database, itu hanya mengembalikan data dari memori karena sudah memuat data dalam memori.


sumber
0

'Bergabung' hanya digunakan untuk bergabung dengan tabel dan ketika Anda memanggil asosiasi bergabung, maka itu akan lagi memunculkan kueri (artinya banyak kueri akan memecat)

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)

jumlah total SQL adalah 11 dalam hal ini

Tetapi dengan 'termasuk' akan bersemangat memuat asosiasi yang disertakan dan menambahkannya dalam memori (memuat semua asosiasi pada beban pertama) dan tidak mem-query lagi

ketika Anda mendapatkan catatan dengan menyertakan like @ records = User.includes (: organization) .where ("organisations.user_id = 1") maka kueri akan menjadi

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} tidak ada permintaan yang akan diaktifkan

Thorin
sumber