Raylar: içerir vs.: birleşimler


345

Bu, "Bunu nasıl yapacağımı bilmiyorum" sorusundan ziyade, "işler neden bu şekilde çalışıyor" sorusundan daha fazlası ...

Bu nedenle, kullanacağınızı bildiğiniz ilişkili kayıtları çekme müjdesi kullanmaktır, :includeçünkü birleştirme elde edersiniz ve bir sürü ekstra sorgudan kaçınırsınız:

Post.all(:include => :comments)

Ancak günlüklere baktığınızda, birleşme gerçekleşmez:

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) 

O edilir aynı anda tüm yorumları çeker çünkü bir kısayol alarak, ama bir katılmak hala değil (ki tüm belgeler söylemek göründüğü gibi). Ben bir katılmak alabilirsiniz tek yolu :joinsyerine kullanmaktır :include:

Post.all(:joins => :comments)

Ve günlükler şunları gösterir:

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

Bir şey mi kaçırıyorum? Yarım düzine dernek ile bir uygulama var ve bir ekranda hepsinden veri görüntülemek. 6 kişi yerine birleştirilmiş bir sorguya sahip olmanın daha iyi olacağı anlaşılıyor. Performans açısından, tek tek sorgulardan ziyade birleştirme yapmak her zaman daha iyi olmadığını biliyorum (aslında harcanan zamana gidiyorsanız, yukarıdaki iki ayrı sorgu birleştirmeden daha hızlı görünüyor), ancak tüm dokümanlardan sonra :includeOkudum, reklamı yapılmadığını görmek beni şaşırttı .

Belki Raylar olduğu performans sorununun vakıf ve bazı durumlar dışında katılmak etmiyor?


3
Rails'in eski bir sürümünü kullanıyorsanız, lütfen bunu etiketlerle veya soru bedeninizde belirtin. Aksi takdirde, ŞİMDİ Rails 4 kullanıyorsanız, bu includes( bunu okuyan herkes için)
onebree

Ayrıca şimdi var: preload ve: eager_load blog.bigbinary.com/2013/07/01/...
CJW

Yanıtlar:


179

:includeRails 2.1 ile işlevsellik değiştirilmiş gibi görünüyor . Raylar her durumda birleştirmeyi yapıyordu, ancak performans nedenlerinden dolayı bazı durumlarda birden çok sorgu kullanıldı. Fabio Akita'nın bu blog yazısı , değişiklik hakkında bazı iyi bilgilere sahiptir ("Optimize Edilmiş İstekli Yükleme" başlıklı bölüme bakın).



Bu çok faydalı, teşekkürler. Rağmen Rails bir 'nerede' gerektiren bir katılmaya zorlamak için bir yol olsaydı. Bazı durumlarda, birleştirmenin daha verimli olacağını ve çoğaltma riskine maruz kalmayacağını biliyorsunuz.
Jonathan Swartz


@JonathanSwartz Yeni versiyona benziyor Rails bunu istekli kullanarak destekliyor . Bağlantı için teşekkürler NathanLong
rubyprince

92

.joinsyalnızca tablolara katılır ve karşılığında seçilen alanları getirir. birleştirmeler sorgu sonucundaki ilişkilendirmeleri çağırırsanız, veritabanı sorgularını yeniden tetikler

:includesdahil edilen ilişkileri yüklemeye ve belleğe eklemeye istekli olacaktır. :includesdahil edilen tüm tablo niteliklerini yükler. Sorgu ekle sonucundaki ilişkilendirmeleri çağırırsanız, sorgular tetiklenmez


71

Join ve include arasındaki fark, include deyimini kullanmanın diğer tablolardan tüm öznitelikleri belleğe yükleyerek çok daha büyük bir SQL sorgusu oluşturmasıdır.

Örneğin, yorumlarla dolu bir tablonuz varsa ve sıralama amacıyla tüm kullanıcı bilgilerini almak için a: joins => kullanıcıları kullanırsanız, iyi çalışır ve aşağıdakilerden daha az zaman alır: dahil etmek, ancak görüntülemek istediğinizi söyleyin kullanıcı adı, e-posta, vb. ile birlikte yorum: kullanarak bilgi almak için: birleşimler, getirdiği her kullanıcı için ayrı SQL sorguları yapmak zorundayken, eğer kullandıysanız: bu bilgileri kullanıma hazır.

Harika örnek:

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


55

Son zamanlarda raylar arasındaki :joinsve :includesraylardaki fark hakkında daha fazla şey okuyordum . İşte anladıklarımın bir açıklaması (örneklerle :))

Şu senaryoyu inceleyin:

  • Bir Kullanıcının has_many yorumları ve yorumu bir Kullanıcıya aittir.

  • Kullanıcı modeli şu niteliklere sahiptir: Ad (dize), Yaş (tamsayı). Yorum modeli şu özelliklere sahiptir: İçerik, kullanıcı_kimliği. Bir yorum için user_id boş olabilir.

Katıldı:

: joins iki tablo arasında bir birleşim gerçekleştirir . Böylece

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 (yorum tablosunun) user.id (kullanıcı tablosu) değerine eşit olduğu tüm kayıtları getirir . Böylece

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

#=> <ActiveRecord::Relation []>

Gösterildiği gibi boş bir dizi alacaksınız.

Ayrıca birleşimler birleştirilen tabloyu belleğe yüklemez. Böylece

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

Gördüğünüz gibi comment_1.user.age, sonuçları almak için arka planda bir veritabanı sorgusu tetikleyecek

İçerir:

: include iki tablo arasında sol dış birleşimi gerçekleştirir . Böylece

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

yorum tablosundaki tüm kayıtları içeren birleştirilmiş tabloyla sonuçlanır . Böylece

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 öğesinin gösterildiği gibi boş olduğu kayıtları getirir.

Ayrıca bellekte her iki tablo yükler içerir. Böylece

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Comment_1.user.age fark edeceğiniz gibi, arka planda bir veritabanı sorgusu başlatmadan sonucu bellekten yükler.


Bu Rails 4 için mi?
onebree

@HunterStevens: Evet, öyle
Aaditi Jain

54

Performans hususlarına ek olarak, fonksiyonel bir fark da vardır. Yorumlara katıldığınızda, varsayılan olarak bir iç bağlantı olan yorum içeren yayınlar istiyorsunuz. Yorum eklediğinizde, tüm yayınları istersiniz - dış birleştirme.


10

tl; Dr.

Onları iki şekilde karşıtlaştırıyorum:

katıldı - Koşullu kayıt seçimi için.

içerir - Sonuç kümesinin her bir üyesinde ilişkilendirme kullanılırken.

Daha uzun versiyon

Birleşimler, veritabanından gelen sonuç kümesine filtre uygulamak içindir. Bunu masanızda ayar işlemleri yapmak için kullanırsınız. Bunu küme teorisini uygulayan bir yer cümlesi olarak düşünün.

Post.joins(:comments)

aynıdır

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

Bunun dışında birden fazla yorum olması durumunda, tekrar birleştirilmiş yayınlar alırsınız. Ancak her gönderi, yorumları olan bir gönderi olacaktır. Bunu farklı bir şekilde düzeltebilirsiniz:

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

Sözleşmede, includesyöntem sadece ilişkiyi referans alırken ek veritabanı sorguları olmadığından emin olacaktır (böylece n + 1 sorguları yapmıyoruz)

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

Ahlaki, joinskoşullu küme işlemleri yapmak istediğinizde kullanın includesve bir koleksiyonun her üyesinde bir ilişki kullanacağınız zaman kullanın .


Bu distinctbeni her zaman yakalar. Teşekkür ederim!
Ben Hull

4

.joins veritabanı birleştirmesi olarak çalışır ve iki veya daha fazla tabloya katılır ve arka uçtan (veritabanı) seçilen verileri alır.

.inc, veritabanının sol birleşimi olarak çalışmayı içerir. Sol taraftaki tüm kayıtları yükledi, sağ taraf modeli ile ilgisi yok. İlişkili tüm nesneleri belleğe yüklediği için yüklemeye istekli olarak kullanılır. Eğer ekleme sorgusu sonucundaki ilişkilendirmeleri çağırırsak, veritabanında bir sorgu başlatmaz, Bellekte zaten veri yüklediği için verileri bellekten döndürür.


0

'birleştirmeler' sadece tabloları birleştirmek için kullanılır ve birleştirmelerdeki ilişkilendirmeleri çağırdığınızda, sorgu tekrar tetiklenir (bu, birçok sorgunun tetikleneceği anlamına gelir)

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)

bu durumda toplam SQL sayısı 11'dir

Ancak 'include' ile birlikte verilen ilişkilendirmeleri yüklemek ve bunları belleğe eklemek isterler (ilk yüklemede tüm ilişkilendirmeleri yükle) ve sorguyu yeniden başlatmaz

include @ users = User.includes (: organisations) .where ("organisations.user_id = 1") içeren kayıtlar aldığınızda sorgu

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} hiçbir sorgu tetiklenmeyecek

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.