Saya memiliki model yang mewakili lukisan yang saya tampilkan di situs saya. Di halaman web utama saya ingin menunjukkan beberapa di antaranya: yang terbaru, yang tidak dikunjungi untuk sebagian besar waktu, yang paling populer, dan yang acak.
Saya menggunakan Django 1.0.2.
Sementara 3 pertama dari mereka mudah ditarik menggunakan model Django, yang terakhir (acak) menyebabkan saya beberapa masalah. Saya dapat ofc kode itu dalam pandangan saya, untuk sesuatu seperti ini:
number_of_records = models.Painting.objects.count()
random_index = int(random.random()*number_of_records)+1
random_paint = models.Painting.get(pk = random_index)
Itu tidak terlihat seperti sesuatu yang saya ingin miliki dalam pandangan saya tho - ini sepenuhnya merupakan bagian dari abstraksi basis data dan harus ada dalam model. Juga, di sini saya perlu mengurus catatan yang dihapus (maka jumlah semua catatan tidak akan mencakup semua nilai kunci yang mungkin) dan mungkin banyak hal lainnya.
Ada pilihan lain bagaimana saya bisa melakukannya, lebih disukai entah bagaimana di dalam abstraksi model?
sumber
Jawaban:
Menggunakan
order_by('?')
akan mematikan server db pada hari kedua dalam produksi. Cara yang lebih baik adalah sesuatu seperti yang dijelaskan dalam Mendapatkan baris acak dari database relasional .sumber
model.objects.aggregate(count=Count('id'))['count']
kelebihanmodel.objects.all().count()
.all()[randint(0, count - 1)]
. Mungkin Anda harus fokus pada mengidentifikasi bagian mana dari jawaban yang salah atau lemah, daripada mendefinisikan ulang "off-by-one-error" untuk kami dan meneriaki pemilih yang bodoh. (Mungkin karena itu tidak digunakan.objects
?)Cukup gunakan:
Itu didokumentasikan dalam API QuerySet .
sumber
random.choice(Model.objects.all())
?Solusi dengan order_by ('?') [: N] sangat lambat bahkan untuk tabel berukuran sedang jika Anda menggunakan MySQL (tidak tahu tentang database lain).
order_by('?')[:N]
akan diterjemahkan keSELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT N
permintaan.Ini berarti bahwa untuk setiap baris dalam tabel fungsi RAND () akan dieksekusi, maka seluruh tabel akan diurutkan sesuai dengan nilai fungsi ini dan kemudian catatan N pertama akan dikembalikan. Jika meja Anda kecil, ini bagus. Tetapi dalam kebanyakan kasus ini adalah permintaan yang sangat lambat.
Saya menulis fungsi sederhana yang berfungsi walaupun id memiliki lubang (beberapa baris tempat dihapus):
Ini lebih cepat daripada order_by ('?') Di hampir semua kasus.
sumber
Inilah solusi sederhana:
sumber
Anda dapat membuat manajer pada model Anda untuk melakukan hal semacam ini. Untuk pertama memahami apa yang seorang manajer, yang
Painting.objects
metode adalah manajer yang berisiall()
,filter()
,get()
, dll Membuat manajer Anda sendiri memungkinkan Anda untuk pra-filter hasil dan memiliki semua metode ini sama, serta metode kustom Anda sendiri, bekerja pada hasil .EDIT : Saya memodifikasi kode saya untuk mencerminkan
order_by['?']
metode ini. Perhatikan bahwa manajer mengembalikan model acak dalam jumlah tidak terbatas. Karena itu, saya memasukkan sedikit kode penggunaan untuk menunjukkan cara mendapatkan hanya satu model.Pemakaian
Terakhir, Anda dapat memiliki banyak manajer pada model Anda, jadi silakan membuat
LeastViewsManager()
atauMostPopularManager()
.sumber
Jawaban lainnya berpotensi lambat (menggunakan
order_by('?')
) atau menggunakan lebih dari satu permintaan SQL. Berikut adalah contoh solusi tanpa pemesanan dan hanya satu permintaan (dengan asumsi Postgres):Perlu diketahui bahwa ini akan meningkatkan kesalahan indeks jika tabel kosong. Tulis sendiri fungsi pembantu model-agnostik untuk memeriksanya.
sumber
count()
terlebih dahulu dan membuang kueri mentah.Hanya ide sederhana bagaimana saya melakukannya:
sumber
Hanya untuk mencatat kasus khusus (yang cukup umum), jika ada kolom kenaikan otomatis terindeks dalam tabel tanpa penghapusan, cara optimal untuk melakukan pemilihan acak adalah kueri seperti:
yang mengasumsikan kolom bernama id untuk tabel. Dalam Django Anda dapat melakukan ini dengan:
di mana Anda harus mengganti appname dengan nama aplikasi Anda.
Secara umum, dengan kolom id, order_by ('?') Dapat dilakukan lebih cepat dengan:
sumber
Ini sangat direkomendasikan.
Memperoleh baris acak dari basis data relasionalKarena menggunakan django orm untuk melakukan hal seperti itu, akan membuat server db Anda marah terutama jika Anda memiliki tabel data besar: |
Dan solusinya adalah menyediakan Model Manager dan menulis permintaan SQL dengan tangan;)
Perbarui :
Solusi lain yang bekerja pada backend basis data apa pun bahkan yang non-rel tanpa menulis kebiasaan
ModelManager
. Mendapatkan objek acak dari Queryset di Djangosumber
Anda mungkin ingin menggunakan pendekatan yang sama dengan yang Anda gunakan untuk sampel iterator apa pun, terutama jika Anda berencana untuk mengambil sampel beberapa item untuk membuat set sampel . @MatijnPieters dan @DzinX menaruh banyak pemikiran dalam hal ini:
sumber
OFFSET
), ini tidak perlu tidak efisien.Salah satu pendekatan yang jauh lebih mudah untuk ini melibatkan hanya menyaring ke catatan menarik dan menggunakan
random.sample
untuk memilih sebanyak yang Anda inginkan:Perhatikan bahwa Anda harus memiliki beberapa kode untuk memverifikasi yang
my_queryset
tidak kosong;random.sample
kembaliValueError: sample larger than population
jika argumen pertama mengandung terlalu sedikit elemen.sumber
Queryset
(setidaknya dengan Python 3.7 dan Django 2.1); Anda harus mengonversikannya ke daftar terlebih dahulu, yang jelas mengambil seluruh queryset.Hai Saya perlu memilih catatan acak dari queryset yang panjangnya saya juga perlu melaporkan (yaitu halaman web menghasilkan item yang dijelaskan dan mengatakan catatan tersisa)
memakan waktu setengah (0,7 vs vs 1,7) sebagai:
Saya kira itu menghindari menarik seluruh permintaan sebelum memilih entri acak dan membuat sistem saya cukup responsif untuk halaman yang diakses berulang kali untuk tugas yang berulang di mana pengguna ingin melihat item_count menghitung mundur.
sumber
Metode untuk menambah kunci primer tanpa penghapusan
Jika Anda memiliki tabel di mana kunci utama adalah integer berurutan tanpa celah, maka metode berikut ini akan berfungsi:
Metode ini jauh lebih efisien daripada metode lain di sini yang mengulangi semua baris tabel. Meskipun membutuhkan dua permintaan basis data, keduanya sepele. Selain itu, ini sederhana dan tidak memerlukan mendefinisikan kelas tambahan Namun, penerapannya terbatas pada tabel dengan kunci primer peningkatan-otomatis di mana baris tidak pernah dihapus, sehingga tidak ada celah dalam urutan id.
Dalam kasus di mana baris telah dihapus sehingga ada celah, metode ini masih bisa berfungsi jika dicoba lagi sampai kunci utama yang ada dipilih secara acak.
Referensi
sumber
Saya mendapat solusi yang sangat sederhana, buat pengelola khusus:
dan kemudian tambahkan model:
Sekarang, Anda bisa menggunakannya:
sumber
order_by('?').first()
lebih dari 60 kali.