Django'nun ORM'sini kullanarak rastgele bir kayıt nasıl çekilir?


176

Sitemde sunduğum resimleri temsil eden bir modelim var. Ana web sayfasında bunlardan bazılarını göstermek istiyorum: en yeni, çoğu zaman ziyaret edilmemiş, en popüler ve rastgele.

Django 1.0.2 kullanıyorum.

İlk 3'ü django modellerini kullanarak çekmek kolay olsa da, sonuncusu (rastgele) bana biraz sorun çıkarıyor. Bence böylesi bir şeye kod yazabilirim:

number_of_records = models.Painting.objects.count()
random_index = int(random.random()*number_of_records)+1
random_paint = models.Painting.get(pk = random_index)

Benim görüşüme göre olmasını istediğim bir şey gibi görünmüyor - bu tamamen veritabanı soyutlamasının bir parçası ve modelde olmalı. Ayrıca, burada kaldırılan kayıtlara dikkat etmeliyim (o zaman tüm kayıtların sayısı bana tüm olası anahtar değerleri kapsamaz) ve muhtemelen diğer birçok şey.

Başka bir seçenek nasıl yapabilirim, tercihen bir şekilde model soyutlamasının içinde?


Bir şeyi nasıl görüntülediğiniz ve hangi şeyleri görüntülediğiniz, bence MVC'nin "Denetleyici" düzeyinde gitmesi gereken "Görünüm" düzeyinin veya iş mantığının bir parçası.
Gabriele D'Antona

Django'da denetleyici görünümdür. docs.djangoproject.com/tr/dev/faq/general/…

Yanıtlar:


169

Kullanarak order_by('?')üretimde ikinci gün db sunucusu öldürür. Daha iyi bir yol, ilişkisel bir veritabanından rastgele bir satır alma bölümünde açıklananlara benzer bir şeydir .

from django.db.models.aggregates import Count
from random import randint

class PaintingManager(models.Manager):
    def random(self):
        count = self.aggregate(count=Count('id'))['count']
        random_index = randint(0, count - 1)
        return self.all()[random_index]

45
model.objects.aggregate(count=Count('id'))['count']Aşırı faydaları nelerdirmodel.objects.all().count()
Ryan Saxe

11
Kabul edilen yanıttan çok daha iyi olsa da, bu yaklaşımın iki SQL sorgusu oluşturduğunu unutmayın. Sayım aralarında değişirse, sınırların dışında bir hata almak mümkün olabilir.
Nelo Mitranim

2
Bu yanlış bir çözüm. Kimlikleriniz 0'dan başlamazsa ve ayrıca kimlikler bitişik olmadığında çalışmaz. Diyelim ki, ilk kayıt 500'den başlıyor ve son kayıt 599 (bitişik olduğu varsayılarak). Sonra sayı 54950 olacaktır. Sorgunuzun uzunluğu 100 olduğundan şüphesiz [54950] listesi mevcut değildir. Dizini ilişkili istisnanın dışına atar. Bu kadar çok insanın neden bu kadar ileri gittiğini bilmiyorum ve bu kabul edilmiş cevap olarak işaretlendi.
sajid

1
@sajid: Neden tam olarak bana soruyorsun? Bu soruya yaptığım katkıların toplamını görmek oldukça kolay: bir arşivi çürükten sonra işaret edecek bir bağlantıyı düzenlemek. Cevapların hiçbirine oy vermedim bile. Ama bu cevabın ve çok daha iyi olduğunu iddia ettiğiniz cevabın her ikisinin de .all()[randint(0, count - 1)]etkili bir şekilde kullandığını eğlenceli buluyorum . Belki de, bizim için "tek tek" yeniden tanımlamak ve aptal seçmenlere bağırmak yerine, cevabın hangi kısmının yanlış veya zayıf olduğunu belirlemeye odaklanmalısınız. (Belki de kullanmıyor .objectsmu?)
Nathan Tuggy

3
@NathanTuggy. Tamam kötü. Üzgünüz
sajid

260

Basitçe kullanın:

MyModel.objects.order_by('?').first()

QuerySet API'sında belgelenmiştir .


71
Bu yaklaşımın belgelendiği gibi çok yavaş olabileceğini lütfen unutmayın :)
Nicolas Dumazet

6
"kullandığınız veritabanı arka ucuna bağlı olarak pahalı ve yavaş olabilir." - farklı DB arka uçları hakkında herhangi bir deneyim? (sqlite / MySQL / postgres)?
kender

4
Bunu test etmedim, bu yüzden bu saf spekülasyon: neden tüm öğeleri almak ve Python'da rasgeleleştirme yapmaktan daha yavaş olmalı?
muhuk

8
mysql inanılmaz derecede verimsiz rastgele sipariş var gibi, mysql yavaş okudum.
Brandon Henry

33
Neden sadece random.choice(Model.objects.all())?
Jamey

25

MySQL kullanıyorsanız (diğer veritabanları hakkında bilgi sahibi değilseniz) order_by ('?') [: N] ile orta ölçekli tablolar için bile son derece yavaş çözümler.

order_by('?')[:N]SELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT Nsorguya çevrilecek .

Bu, tablodaki her satır için RAND () işlevinin yürütüleceği, tüm tablonun bu işlevin değerine göre sıralanacağı ve ardından ilk N kayıtlarının döndürüleceği anlamına gelir. Tablolarınız küçükse, bu iyi. Ancak çoğu durumda bu çok yavaş bir sorgudur.

Kimliğin delikleri olsa bile çalışan basit bir işlev yazdım (bazı satırlar silindi):

def get_random_item(model, max_id=None):
    if max_id is None:
        max_id = model.objects.aggregate(Max('id')).values()[0]
    min_id = math.ceil(max_id*random.random())
    return model.objects.filter(id__gte=min_id)[0]

Hemen hemen tüm durumlarda order_by'den ('?') Daha hızlıdır.


30
Ayrıca, ne yazık ki, rastgele olmaktan uzak. 1 numaralı ve 100 numaralı kimliğe sahip başka bir kaydınız varsa, ikinci kaydı% 99 oranında döndürür.
DS.

16

İşte basit bir çözüm:

from random import randint

count = Model.objects.count()
random_object = Model.objects.all()[randint(0, count - 1)] #single random object

10

Bu tür bir şeyi yapmak için modelinizde bir yönetici oluşturabilirsiniz . Önce bir yönetici ne olduğunu anlamak için, Painting.objectsyöntem içeren bir yöneticisidir all(), filter(), get()kendi yöneticisi oluşturma, vb-ön filtre sonuçları ve sonuçları üzerinde bütün bu aynı yöntemleri yanı sıra kendi özel yöntemleri, iş var sağlar .

EDIT : order_by['?']yöntemimi yansıtacak şekilde kodumu değiştirdim . Yöneticinin sınırsız sayıda rastgele model döndürdüğünü unutmayın. Bu nedenle, sadece tek bir modelin nasıl alınacağını göstermek için biraz kullanım kodu ekledim.

from django.db import models

class RandomManager(models.Manager):
    def get_query_set(self):
        return super(RandomManager, self).get_query_set().order_by('?')

class Painting(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    randoms = RandomManager() # The random-specific manager.

kullanım

random_painting = Painting.randoms.all()[0]

Son olarak, modellerinizde birçok yöneticiniz olabilir, bu yüzden bir LeastViewsManager()veya oluşturmaktan çekinmeyin MostPopularManager().


3
Get () işlevini kullanmak, pks'leriniz ardışıksa, yani hiçbir öğeyi asla silmediğinizde işe yarar. Aksi takdirde, var olmayan bir pk almaya çalışabilirsiniz. .All () [random_index] kullanmak bu sorundan muzdarip değildir ve daha az verimli değildir.
Daniel Roseman

Bu yüzden örneğimin sorunun kodunu bir yöneticiyle tekrarladığını anladım. Sınır kontrolünü yapmak yine de OP'ye bağlı olacak.
Soviut

1
.get (id = random_index) yerine .filter (id__gte = random_index) [0: 1] kullanmak daha iyi olmaz mıydı? İlk olarak, problemi ardışık olmayan pks ile çözmeye yardımcı olur. İkinci olarak, get_query_set bir QuerySet döndürmelidir. Ve örneğinizde, öyle değil.
Nicolas Dumazet

2
Sadece bir yöntemi barındırmak için yeni bir yönetici oluşturmazdım. Rastgele görüntüye her ihtiyacınız olduğunda all () [0] çemberinden geçmek zorunda kalmamanız için varsayılan yöneticiye "get_random" eklerdim. Ayrıca, yazar bir Kullanıcı modeline bir Yabancı Anahtar ise, user.painting_set.get_random () diyebilirsiniz.
Antti Rasinen

Rastgele kayıtların bir listesini almak gibi bir battaniye eylemi istediğimde genellikle yeni bir yönetici oluştururum. Zaten sahip olduğum kayıtlarla daha spesifik bir görev yapsaydım, varsayılan yöneticide bir yöntem yaratırdım.
Soviut

6

Diğer yanıtlar ya potansiyel olarak yavaştır (kullanarak order_by('?')) ya da birden fazla SQL sorgusu kullanır. İşte sipariş ve sadece bir sorgu (Postgres varsayarak) ile örnek bir çözüm:

Model.objects.raw('''
    select * from {0} limit 1
    offset floor(random() * (select count(*) from {0}))
'''.format(Model._meta.db_table))[0]

Tablo boşsa, bunun bir dizin hatasını artıracağını unutmayın. Bunu kontrol etmek için kendinize model-agnostik bir yardımcı fonksiyon yazın.


Kavramın güzel bir kanıtı, ancak bu veritabanı içinde de iki sorgu, ne tasarruf veritabanına bir gidiş dönüş. Buna değen ham bir sorgu yazma ve sürdürme yapmak için bunu birçok kez yürütmeniz gerekir. Ve boş tablolara karşı korunmak istiyorsanız count(), önceden de çalıştırabilir ve ham sorgudan vazgeçebilirsiniz.
Endre Her ikisi de

2

Nasıl yapacağımı basit bir fikir:

def _get_random_service(self, professional):
    services = Service.objects.filter(professional=professional)
    i = randint(0, services.count()-1)
    return services[i]

1

(Oldukça yaygın) özel bir durumu not etmek gerekirse, tabloda silmeden dizinlenmiş bir otomatik artış sütunu varsa, rastgele bir seçim yapmanın en iyi yolu şudur:

SELECT * FROM table WHERE id = RAND() LIMIT 1

tablo için id adında böyle bir sütun varsayar. Django'da bunu şu şekilde yapabilirsiniz:

Painting.objects.raw('SELECT * FROM appname_painting WHERE id = RAND() LIMIT 1')

uygulama adını uygulama adınızla değiştirmeniz gerekir.

Genel olarak, bir id sütunuyla, order_by ('?') Aşağıdakilerle çok daha hızlı yapılabilir:

Paiting.objects.raw(
        'SELECT * FROM auth_user WHERE id>=RAND() * (SELECT MAX(id) FROM auth_user) LIMIT %d' 
    % needed_count)

1

Bu şiddetle tavsiye edilir İlişkisel bir veritabanından rastgele bir satır alma

Çünkü böyle bir şey yapmak için django orm kullanarak, büyük veri tablosu varsa, db sunucunuzu özellikle kızgın yapar: |

Ve çözüm bir Model Yöneticisi sağlamak ve elle SQL sorgusu yazmak;)

Güncelleme :

Herhangi bir veritabanı üzerinde çalışan ve rel olmayanları bile özel yazmadan çalışan başka bir çözüm ModelManager. Django'da Queryset'ten Rasgele nesneler alma


1

Özellikle bir örnek küme oluşturmak için birden çok öğeyi örneklemeyi planlıyorsanız, herhangi bir yineleyiciyi örneklemek için kullandığınız aynı yaklaşımı kullanmak isteyebilirsiniz . @MatijnPieters ve @DzinX bu konuda çok düşünüyor:

def random_sampling(qs, N=1):
    """Sample any iterable (like a Django QuerySet) to retrieve N random elements

    Arguments:
      qs (iterable): Any iterable (like a Django QuerySet)
      N (int): Number of samples to retrieve at random from the iterable

    References:
      @DZinX:  https://stackoverflow.com/a/12583436/623735
      @MartinPieters: https://stackoverflow.com/a/12581484/623735
    """
    samples = []
    iterator = iter(qs)
    # Get the first `N` elements and put them in your results list to preallocate memory
    try:
        for _ in xrange(N):
            samples.append(iterator.next())
    except StopIteration:
        raise ValueError("N, the number of reuested samples, is larger than the length of the iterable.")
    random.shuffle(samples)  # Randomize your list of N objects
    # Now replace each element by a truly random sample
    for i, v in enumerate(qs, N):
        r = random.randint(0, i)
        if r < N:
            samples[r] = v  # at a decreasing rate, replace random items
    return samples

Matijn ve DxinX'in çözümü, rasgele erişim sağlamayan veri kümeleri içindir. Bunu yapan (ve SQL ile birlikte OFFSET) veri kümeleri için bu gereksiz yere verimsizdir.
Her ikisi de

@EndreBoth gerçekten. Veri kaynağından bağımsız olarak aynı yaklaşımı kullanmanın “verimliliğini” kodlamayı seviyorum. Bazen veri örnekleme verimliliği, diğer süreçlerle (ML eğitimi gibi verilerle ne yaparsanız yapın) sınırlı bir boru hattının performansını önemli ölçüde etkilemez.
ocaklar

1

Buna daha kolay bir yaklaşım, sadece ilgili kayıt kümesine filtre uygulamak ve random.sampleistediğiniz kadarını seçmek için kullanmaktır :

from myapp.models import MyModel
import random

my_queryset = MyModel.objects.filter(criteria=True)  # Returns a QuerySet
my_object = random.sample(my_queryset, 1)  # get a single random element from my_queryset
my_objects = random.sample(my_queryset, 5)  # get five random elements from my_queryset

my_querysetBoş olmadığını doğrulamak için bazı kodların olması gerektiğini unutmayın ; ilk bağımsız değişken çok az öğe içeriyorsa random.sampledöndürür ValueError: sample larger than population.


2
Bu, tüm sorgu kümesinin alınmasına neden olur mu?
perrohunter

@perrohunter Şununla bile çalışmaz Queryset(en azından Python 3.7 ve Django 2.1 ile); önce bir listeye dönüştürmeniz gerekir, ki bu da tüm sorgu kümesini alır.
Endre Her ikisi de

@EndreBoth - bu 2016 yılında, bunların hiçbirinin mevcut olmadığı yazılmıştır.
eykanal

Bu yüzden sürüm bilgisini ekledim. Ancak 2016'da işe yaradıysa, tüm sorgu kümesini bir listeye çekerek yaptı, değil mi?
Endre Her ikisi de

@EndreBoth Doğru.
eykanal

1

Merhaba ben de rapor gereken bir queryset rastgele bir kayıt seçmek için gerekli (yani web sayfası açıklanan öğe ve kalan kayıtları üretilen)

q = Entity.objects.filter(attribute_value='this or that')
item_count = q.count()
random_item = q[random.randomint(1,item_count+1)]

yarısı kadar aldı (0.7s vs 1.7s):

item_count = q.count()
random_item = random.choice(q)

Rastgele girişi seçmeden önce tüm sorguyu aşağı çekmeyi önler ve sistemimi kullanıcıların item_count geri sayımını görmek istediği tekrarlayan bir görev için tekrar tekrar erişilen bir sayfa için yeterince duyarlı hale getirdiğini tahmin ediyorum.


0

Birincil anahtarı silmeden otomatik olarak arttırma yöntemi

Birincil anahtarın boşluk içermeyen sıralı bir tamsayı olduğu bir tablonuz varsa, aşağıdaki yöntem çalışmalıdır:

import random
max_id = MyModel.objects.last().id
random_id = random.randint(0, max_id)
random_obj = MyModel.objects.get(pk=random_id)

Bu yöntem, tablonun tüm satırlarında yinelenen diğer yöntemlerden çok daha etkilidir. İki veritabanı sorgusu gerektirse de, her ikisi de önemsizdir. Ayrıca, basittir ve ekstra sınıf tanımlamayı gerektirmez. Bununla birlikte, uygulanabilirliği, satırların hiç silinmediği otomatik artan bir birincil anahtara sahip tablolarla sınırlıdır, böylece kimlikler dizisinde boşluk olmaz.

Satırların boşluk olacak şekilde silinmesi durumunda, bu yöntem mevcut bir birincil anahtar rastgele seçilene kadar yeniden denenirse yine de çalışabilir.

Referanslar


0

Çok basit bir çözüm buldum, özel yönetici yap:

class RandomManager(models.Manager):
    def random(self):
        return random.choice(self.all())

ve sonra modeli ekleyin:

class Example(models.Model):
    name = models.CharField(max_length=128)
    objects = RandomManager()

Şimdi kullanabilirsiniz:

Example.objects.random()

rasgele ithalat seçiminden
Adam Starrh

3
Hız istiyorsanız, lütfen bu yöntemi kullanmayın. Bu çözüm ÇOK yavaştır. Kontrol ettim. order_by('?').first()60 kattan daha yavaş .
LagRange

@ Alex78191 hayır, "?" kötü, ama benim yöntem EXTRA yavaş. En iyi cevap çözümünü kullandım.
LagRange
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.