Pertimbangkan asosiasi sederhana ...
class Person
has_many :friends
end
class Friend
belongs_to :person
end
Apa cara paling bersih untuk mendapatkan semua orang yang TIDAK memiliki teman di ARel dan / atau meta_where?
Lalu bagaimana dengan has_many: melalui versi
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
end
class Friend
has_many :contacts
has_many :people, :through => :contacts, :uniq => true
end
class Contact
belongs_to :friend
belongs_to :person
end
Saya benar-benar tidak ingin menggunakan counter_cache - dan saya dari apa yang saya baca tidak bekerja dengan has_many: through
Saya tidak ingin menarik semua catatan person.friends dan mengulanginya di Ruby - Saya ingin memiliki kueri / lingkup yang dapat saya gunakan dengan permata meta_search
Saya tidak keberatan dengan biaya kinerja dari kueri
Dan semakin jauh dari SQL aktual semakin baik ...
ruby-on-rails
arel
meta-where
craic.com
sumber
sumber
DISTINCT
. Kalau tidak, saya pikir Anda ingin menormalkan data dan indeks dalam kasus itu. Saya mungkin melakukannya dengan membuatfriend_ids
kolom hstore atau serial. Maka Anda bisa mengatakanPerson.where(friend_ids: nil)
not exists (select person_id from friends where person_id = person.id)
(Atau mungkinpeople.id
ataupersons.id
, tergantung pada apa meja Anda.) Tidak yakin apa yang tercepat dalam situasi tertentu, tetapi di masa lalu ini telah bekerja dengan baik untuk saya ketika saya tidak mencoba menggunakan ActiveRecord.Lebih baik:
Untuk hmt pada dasarnya hal yang sama, Anda bergantung pada kenyataan bahwa seseorang tanpa teman juga tidak akan memiliki kontak:
Memperbarui
Punya pertanyaan tentang
has_one
di komentar, jadi baru saja memperbarui. Kuncinya di sini adalah bahwaincludes()
mengharapkan nama asosiasi tetapiwhere
mengharapkan nama tabel. Untuk suatuhas_one
asosiasi umumnya akan diekspresikan dalam bentuk tunggal, sehingga berubah, tetapiwhere()
bagian tetap seperti itu. Jadi, jikaPerson
hanya satuhas_one :contact
maka pernyataan Anda adalah:Perbarui 2
Seseorang bertanya tentang kebalikannya, teman tanpa orang. Seperti yang saya komentari di bawah ini, ini benar-benar membuat saya menyadari bahwa bidang terakhir (di atas: the
:person_id
) tidak benar-benar harus terkait dengan model yang Anda kembalikan, hanya harus berupa bidang di tabel bergabung. Mereka semua akannil
jadi bisa salah satu dari mereka. Ini mengarah ke solusi yang lebih sederhana untuk yang di atas:Dan kemudian beralih ini untuk mengembalikan teman tanpa orang menjadi lebih sederhana, Anda hanya mengubah kelas di depan:
Perbarui 3 - Rel 5
Terima kasih kepada @Anson untuk solusi Rails 5 yang sangat baik (beri dia +1 untuk jawabannya di bawah), Anda dapat menggunakan
left_outer_joins
untuk menghindari memuat asosiasi:Saya sudah memasukkannya di sini sehingga orang-orang akan menemukannya, tetapi dia layak mendapatkan +1 untuk ini. Tambahan yang bagus!
Pembaruan 4 - Rel 6.1
Terima kasih kepada Tim Park karena telah menunjukkan bahwa pada 6.1 mendatang Anda dapat melakukan ini:
Berkat pos yang dia tautkan juga.
sumber
has_one
asosiasi Anda, Anda perlu mengubah nama asosiasi dalamincludes
panggilan. Jadi dengan asumsi itu adahas_one :contact
di dalamPerson
maka kode Anda akanPerson.includes(:contact).where( :contacts => { :person_id => nil } )
self.table_name = "custom_friends_table_name"
), maka gunakanPerson.includes(:friends).where(:custom_friends_table_name => {:id => nil})
.missing
metode untuk melakukan hal ini !smathy memiliki jawaban Rails 3 yang bagus.
Untuk Rails 5 , Anda dapat menggunakan
left_outer_joins
untuk menghindari memuat asosiasi.Lihat dokumen api . Itu diperkenalkan dalam permintaan tarik # 12071 .
sumber
.includes
biaya tambahan dalam waktu buka tidak akan menjadi sesuatu yang saya khawatirkan tentang pengoptimalan. Kasing penggunaan Anda mungkin berbeda.Person.joins('LEFT JOIN contacts ON contacts.person_id = persons.id').where('contacts.id IS NULL')
Ini berfungsi dengan baik sebagai ruang lingkup juga. Saya melakukan ini sepanjang waktu di proyek Rails saya.includes
, semua objek AR tersebut dimuat ke dalam memori, yang bisa menjadi hal yang buruk karena tabel semakin besar dan besar. Jika Anda tidak memerlukan akses ke catatan kontak,left_outer_joins
itu tidak memuat kontak ke dalam memori. Kecepatan permintaan SQL adalah sama, tetapi keseluruhan manfaat aplikasi jauh lebih besar.Person.where(contacts: nil)
atauPerson.with(contact: contact)
jika menggunakan di mana melanggar terlalu jauh ke 'kelayakan' - tetapi mengingat kontak itu: sudah diurai dan diidentifikasi sebagai sebuah asosiasi, tampaknya logis bahwa arel dapat dengan mudah mengetahui apa yang diperlukan ...Orang yang tidak memiliki teman
Atau yang memiliki setidaknya satu teman
Anda dapat melakukan ini dengan Arel dengan menyiapkan cakupan
Friend
Dan kemudian, Orang yang memiliki setidaknya satu teman:
The friendless:
sumber
DEPRECATION WARNING: It looks like you are eager loading table(s)
Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string
Baik jawaban dari dmarkow dan Unixmonkey memberi saya apa yang saya butuhkan - Terima kasih!
Saya mencoba keduanya di aplikasi asli saya dan mendapatkan timing untuk mereka - Berikut adalah dua cakupannya:
Jalankan ini dengan aplikasi nyata - meja kecil dengan ~ 700 catatan 'Person' - rata-rata 5 berjalan
Pendekatan Unixmonkey (
:without_friends_v1
) 813ms / permintaanpendekatan dmarkow (
:without_friends_v2
) 891ms / permintaan (~ 10% lebih lambat)Tetapi kemudian terlintas dalam benak saya bahwa saya tidak memerlukan panggilan untuk
DISTINCT()...
mencariPerson
catatan dengan TIDAKContacts
- jadi mereka hanya perlu menjadiNOT IN
daftar kontakperson_ids
. Jadi saya mencoba cakupan ini:Itu mendapatkan hasil yang sama tetapi dengan rata-rata 425 ms / panggilan - hampir separuh waktu ...
Sekarang Anda mungkin perlu
DISTINCT
dalam pertanyaan serupa lainnya - tetapi untuk kasus saya ini sepertinya berfungsi dengan baik.Terima kasih atas bantuan Anda
sumber
Sayangnya, Anda mungkin melihat solusi yang melibatkan SQL, tetapi Anda bisa mengaturnya dalam lingkup dan kemudian gunakan lingkup itu:
Kemudian untuk mendapatkannya, Anda bisa melakukannya
Person.without_friends
, dan Anda juga bisa mengaitkannya dengan metode Arel lainnya:Person.without_friends.order("name").limit(10)
sumber
A TIDAK ADA subquery berkorelasi harus cepat, terutama karena jumlah baris dan rasio catatan anak ke orang tua meningkat.
sumber
Juga, untuk memfilter oleh satu teman misalnya:
sumber