Pertanyaan Optimalkan pertanyaan sulit (mungkin dengan squeel)


Ada kode seperti itu (menggunakan PublicActivity gem & Squeel)

  def index
    @activities = Activity.limit(20).order { created_at.desc }
    @one = @activities.where{trackable_type == 'Post'}.includes(trackable: [:author, :project])
    @two = @activities.where{trackable_type == 'Project'}.includes trackable: [:owner]
    @activities = @one + @two
  end

Tetapi itu menciptakan 8 Permintaan SQL:

 SELECT "activities".* FROM "activities" WHERE "activities"."trackable_type" = 'Post' ORDER BY "activities"."created_at" DESC LIMIT 20

      SELECT "posts".* FROM "posts" WHERE "posts"."id" IN (800, 799, 798, 797, 796, 795, 794, 793, 792, 791, 790, 789, 788, 787, 786, 785, 784, 783, 782, 781)

      SELECT "users".* FROM "users" WHERE "users"."id" IN (880, 879, 878, 877, 876, 875, 874, 873, 872, 871, 869, 868, 867, 866, 865, 864, 863, 862, 861, 860)

      SELECT "projects".* FROM "projects" WHERE "projects"."id" IN (80, 79)

      SELECT "activities".* FROM "activities" WHERE "activities"."trackable_type" = 'Project' ORDER BY "activities"."created_at" DESC LIMIT 20

      SELECT "projects".* FROM "projects" WHERE "projects"."id" IN (80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61)

     SELECT "users".* FROM "users" WHERE "users"."id" IN (870, 859, 848, 837, 826, 815, 804, 793, 782, 771, 760, 749, 738, 727, 716, 705, 694, 683, 672, 661)
  1. permintaan aktif tidak bergabung
  2. beberapa pengguna (pemilik posting dan pemilik proyek) dimuat dua kali
  3. beberapa proyek dimuat dua kali
  4. @activities adalah Array. Relasi rel menggabungkan metode (kecuali +) tidak berfungsi dengan kode di atas.

Ada gagasan untuk mengoptimalkannya?


4
2017-10-11 16:07


asal


Jawaban:


Singkatnya, Anda tidak dapat mengoptimalkan lebih lanjut tanpa menggunakan SQL. Ini adalah cara Rails melakukan bisnis. Ini tidak mengizinkan akses untuk menggabungkan bidang di luar model AR tempat kueri diajukan. Oleh karena itu untuk mendapatkan nilai di tabel lain, ia melakukan query pada masing-masing.

Itu juga tidak memungkinkan UNION atau mewah WHERE kondisi yang menyediakan cara lain untuk memecahkan masalah.

Kabar baiknya adalah bahwa pertanyaan-pertanyaan ini adalah semua yang efisien (mengingat bahwa trackable_type diindeks). Jika ukuran hasil adalah sesuatu yang substansial (katakanlah beberapa lusin baris), waktu i / o akan mendominasi sedikit tambahan overhead dari 7 pertanyaan sederhana wakil 1 kompleks.

Bahkan menggunakan SQL, akan sulit untuk mendapatkan semua hasil gabung yang Anda inginkan dalam satu permintaan. (Hal ini dapat dilakukan, tetapi hasilnya akan berupa hash daripada turunan AR. Jadi kode dependen akan menjadi jelek.) Satu-query-per-tabel ditransfer cukup dalam ke Rekaman Aktif.

Solusi @Yoshi adalah kompromi yang baik menggunakan SQL minimal, tetapi tidak memungkinkan Anda untuk memuat secara terpisah author atau project+owner  berdasarkan pada trackable_type bidang.

Edit

Di atas adalah semua benar untuk Rails 3. Untuk Rails 4 sebagai @ CMW mengatakan, the eager_load metode akan melakukan hal yang sama includes menggunakan gabungan luar, bukan kueri terpisah. Inilah mengapa saya suka SO! Saya selalu belajar sesuatu.


1
2017-10-25 16:06



Larutan non-rel-4, non-squeel adalah:

def index
  @activities = Activity.limit(20).order("created_at desc")
  @one = @activities.where(trackable_type: 'Post')   .joins(trackable: [:author, :project]).includes(trackable: [:author, :project])
  @two = @activities.where(trackable_type: 'Project').joins(trackable: [:owner])           .includes(trackable: [:owner])
  @activities = @one + @two
end

Kombinasi dari joins dan includes terlihat aneh, tetapi dalam pengujian saya itu bekerja dengan sangat baik.

Ini akan menguranginya menjadi dua kueri, bukan satu. Dan @activities akan tetap oleh larik. Tapi mungkin menggunakan pendekatan ini dengan squeel akan menyelesaikan itu juga. Saya tidak menggunakan squeel dan tidak bisa mengujinya, sayangnya.

EDIT: Saya benar-benar kehilangan intisari tentang asosiasi polimorfik ini. Cara di atas bekerja untuk memaksa

Jika Anda ingin menggunakan apa yang ditawarkan oleh AR, itu sedikit bersifat peretasan tetapi Anda dapat mendefinisikan proyek dan pos terkait hanya-baca:

belongs_to :project, read_only: true, foreign_key: :trackable_id
belongs_to :post,    read_only: true, foreign_key: :trackable_id

Dengan cara-cara yang disebutkan di atas, memaksa beban bersemangat harus bekerja. Itu where kondisi masih dibutuhkan, sehingga asosiasi tersebut hanya dipanggil untuk kegiatan yang tepat.

def index
  @activities = Activity.limit(20).order("created_at desc")
  @one = @activities.where(trackable_type: 'Post')   .joins(post: [:author, :project]).includes(post: [:author, :project])
  @two = @activities.where(trackable_type: 'Project').joins(project: [:owner])        .includes(project: [:owner])
  @activities = @one + @two
end

Ini bukan solusi yang bersih dan asosiasi harus di -roteksi untuk memastikan mereka tidak diatur secara tidak sengaja (yang akan merusak polimorfisme, saya kira), tetapi dari pengujian saya tampaknya berhasil.


2
2017-10-22 20:24



Menggunakan kasus Switch sederhana di SQL:

def index
  table_name = Activity.table_name
  @activities = Activity.where(trackable_type: ['Post', 'Project'])
                        .order("CASE #{table_name}.owner_type WHEN 'Post' THEN 'a' ELSE 'z' END, #{table_name}.created_at DESC")
end

Maka Anda dapat dengan mudah menambahkan termasuk yang Anda inginkan;)


1
2017-10-18 17:07



Saya percaya Anda akan membutuhkan setidaknya dua permintaan query AR (seperti yang Anda miliki saat ini) karena limit(20)ayat. Permintaan Anda saat ini memberi Anda hingga 20 Tulisan, DAN hingga 20 Proyek, sehingga melakukan batas agregat pada kedua jenis aktivitas dalam satu permintaan tidak akan memberikan hasil yang diinginkan.

Saya pikir yang perlu Anda lakukan hanyalah menggunakan eager_load dalam kueri sebagai ganti includes untuk memaksa satu permintaan. Perbedaan antara joins, includes, preload, eager_load dan references metode tertutup dengan baik sini

Jadi, dengan AR dan squeel:

def index
    @activities = Activity.limit(20).order { created_at.desc }
    @one = @activities.where{trackable_type == 'Post'}.eager_loads(trackable: [:author, :project])
    @two = @activities.where{trackable_type == 'Project'}.eager_loads trackable: [:owner]
    @activities = @one + @two
end

Dan tanpa squeel, hanya menggunakan ActiveRecord 4 biasa:

def index
    @activities = Activity.limit(20).order(created_at: :desc)
    @one = @activities.where(trackable_type: 'Post').eager_loads(trackable: [:author, :project])
    @two = @activities.where(trackable_type: 'Project').eager_loads(trackable: :owner)
    @activities = @one + @two
end

Anda tidak perlu squeel, saya baru-baru ini merobeknya keluar dari proyek saya karena tidak berfungsi dengan baik untuk sejumlah pertanyaan kompleks dalam pengalaman saya, di mana AR 4 dan Arel baik-baik saja.


1
2017-10-21 12:24



Itu adalah permintaan yang cukup besar di sana ... dengan kelihatannya Anda dapat melakukannya dalam satu pilihan, tetapi untuk mudah dibaca saya akan menggunakan dua, satu untuk proyek dan satu untuk posting.

Ini mengasumsikan hubungan 1: 1 antara aktivitas dan pos / proyek. Jika ini tidak benar, masalahnya bisa diselesaikan dengan subquery

select * from activities a
where a.trackable_type = 'Post'
left join posts p
on p.id = a.trackable_id -- or whatever fields join these two tables
left join users u
on a.user_id = u.id --this is joining to the main table, may want to join trackable, not sure
left join projects p
on a.project_id = p.id
order by a.created_at DESC LIMIT 20

Atau, jika ada hubungan 1: banyak, sesuatu seperti ini:

select * from
(   select * from activities a
    where a.trackable_type = 'Post'
    order by a.created_at DESC LIMIT 20 ) activities
left join posts p
...

Edit: Ketika saya membaca ini, saya menyadari bahwa saya agak kuno .... Saya pikir jika Anda akan menggunakan query sql mentah besar seperti itu, Anda harus membuat fungsi database, daripada mengkodekannya ke dalam aplikasi Anda


0
2017-10-11 19:15