Django के ORM का उपयोग करके एक यादृच्छिक रिकॉर्ड कैसे खींचना है?


176

मेरे पास एक मॉडल है जो मेरी साइट पर मौजूद चित्रों का प्रतिनिधित्व करता है। मुख्य वेबपेज पर मैं उनमें से कुछ दिखाना चाहूंगा: सबसे नया, एक जो सबसे अधिक समय तक नहीं देखा गया था, सबसे लोकप्रिय एक और एक यादृच्छिक।

मैं Django 1.0.2 का उपयोग कर रहा हूं।

जबकि उनमें से पहले 3 django मॉडल का उपयोग करना आसान है, पिछले एक (यादृच्छिक) ने मुझे कुछ परेशानी दी। मैं इसे अपने विचार में कुछ इस तरह से कोड कर सकता हूं:

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

यह ऐसा कुछ नहीं दिखता है जिसे मैं अपने विचार में रखना चाहता हूं - यह पूरी तरह से डेटाबेस अमूर्त का हिस्सा है और मॉडल में होना चाहिए। इसके अलावा, यहां मुझे हटाए गए अभिलेखों की देखभाल करने की आवश्यकता है (तब सभी रिकॉर्डों की संख्या मुझे सभी संभावित प्रमुख मूल्यों को कवर नहीं करेगी) और शायद बहुत सी अन्य चीजें।

कोई अन्य विकल्प कि मैं यह कैसे कर सकता हूं, अधिमानतः मॉडल अमूर्त के अंदर किसी तरह?


आप चीजों को कैसे प्रदर्शित करते हैं और आप किन चीजों को प्रदर्शित करते हैं "व्यू" स्तर या व्यावसायिक तर्क का एक हिस्सा है जो मेरी राय में "एमवीसी के नियंत्रक" स्तर पर जाना चाहिए।
गैब्रियल डी'अनटन

Django में नियंत्रक दृश्य है। docs.djangoproject.com/en/dev/faq/general/…

जवाबों:


169

उपयोग करने order_by('?')से उत्पादन में दूसरे दिन db सर्वर की मृत्यु हो जाएगी। एक बेहतर तरीका कुछ ऐसा है जो एक रिलेशनल डेटाबेस से यादृच्छिक पंक्ति प्राप्त करने में वर्णित है ।

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']ओवर के क्या लाभ हैंmodel.objects.all().count()
रयान सक्से

11
स्वीकृत उत्तर की तुलना में बहुत बेहतर होने पर, ध्यान दें कि यह दृष्टिकोण दो SQL क्वेरी बनाता है। यदि बीच में गिनती बदल जाती है, तो सीमा त्रुटि से बाहर निकलना संभव हो सकता है।
नेल्लो मित्रानिम

2
यह एक गलत समाधान है। यह काम नहीं करेगा यदि आपकी आईडी 0. से शुरू नहीं होती है और यह भी जब आईडी सन्निहित नहीं हैं। कहते हैं, पहला रिकॉर्ड 500 से शुरू होता है और अंतिम 599 (आकस्मिकता को मानते हुए) है। फिर गिनती 54950 होगी। निश्चित रूप से सूची [54950] मौजूद नहीं है, क्योंकि आपके queryst की लंबाई 100 है। यह सूचकांक को अपवाद से बाहर फेंक देगा। मुझे नहीं पता कि इतने लोगों ने इसे क्यों उखाड़ा और इसे स्वीकृत उत्तर के रूप में चिह्नित किया गया।
साजिद

1
@ साजिद: क्यों, बिल्कुल, क्या आप मुझसे पूछ रहे हैं? इस प्रश्न के लिए मेरे योगदानों का कुल योग देखना काफी आसान है: एक लिंक को इंगित करने के लिए एक संग्रह को संपादित करने के बाद इसे रॉट किया गया। मैंने किसी भी उत्तर पर मतदान नहीं किया है। लेकिन मुझे यह मनोरंजक लगता है कि यह उत्तर और जिस पर आप दावा करते हैं, दोनों .all()[randint(0, count - 1)]प्रभाव में बेहतर होते हैं । हो सकता है कि आपको यह पहचानने पर ध्यान देना चाहिए कि हमारे लिए "ऑफ-बाय-वन-एरर" को फिर से परिभाषित करने और मूर्ख मतदाताओं को चिल्लाकर जवाब देने का क्या हिस्सा गलत या कमजोर है। (शायद यह है कि यह उपयोग नहीं कर रहा है .objects?)
नाथन तुग्गी

3
@NathanTuggy। ठीक है मेरा बुरा क्षमा करें
साजिद

260

बस उपयोग करें:

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

यह QuerySet API में प्रलेखित है


71
कृपया ध्यान दें कि यह दृष्टिकोण बहुत धीमा हो सकता है, जैसा कि प्रलेखित :)
निकोलस डुमाज़ेट

6
"आपके द्वारा उपयोग किए जा रहे डेटाबेस बैकएंड के आधार पर महंगा और धीमा हो सकता है।" - डिफरेंट डीबी बैकएंड पर कोई अनुभव? (SQLite / mysql / postgres)?
जेंडर

4
मैंने इसका परीक्षण नहीं किया है, इसलिए यह शुद्ध अटकलें हैं: सभी वस्तुओं को प्राप्त करने और पायथन में यादृच्छिककरण करने की तुलना में यह धीमा क्यों होना चाहिए?
मुहुक

8
मैंने पढ़ा कि यह mysql में धीमा है, क्योंकि mysql में अविश्वसनीय रूप से अक्षम यादृच्छिक क्रम है।
ब्रैंडन हेनरी

33
सिर्फ क्यों नहीं random.choice(Model.objects.all())?
जामेई

25

यदि आप MySQL (अन्य डेटाबेस के बारे में नहीं जानते हैं) का उपयोग करते हुए मध्यम-आकार की तालिकाओं के लिए भी order_by ('?') [: N] के साथ समाधान बेहद धीमा है।

order_by('?')[:N]SELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT Nक्वेरी के लिए अनुवादित किया जाएगा ।

इसका मतलब है कि तालिका में प्रत्येक पंक्ति के लिए रैंड () फ़ंक्शन निष्पादित किया जाएगा, फिर इस फ़ंक्शन के मूल्य के अनुसार पूरी तालिका को सॉर्ट किया जाएगा और फिर पहले एन रिकॉर्ड वापस किए जाएंगे। यदि आपकी टेबल छोटी हैं, तो यह ठीक है। लेकिन ज्यादातर मामलों में यह बहुत धीमी क्वेरी है।

मैंने सरल कार्य लिखा है जो आईडी के छेद होने पर भी काम करता है (कुछ पंक्तियाँ जहाँ हटाई गई हैं):

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]

यह लगभग सभी मामलों में order_by ('?') से तेज़ है।


30
इसके अलावा, दुख की बात है, यह यादृच्छिक से बहुत दूर है। यदि आपके पास आईडी 1 और दूसरा आईडी 100 के साथ रिकॉर्ड है, तो यह समय का दूसरा एक 99% वापस कर देगा।
डी.एस.

16

यहाँ एक सरल उपाय है:

from random import randint

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

10

आप इस तरह की बात करने के लिए अपने मॉडल पर एक प्रबंधक बना सकते हैं । पहले समझने के लिए क्या एक प्रबंधक है, Painting.objectsविधि एक प्रबंधक है कि होता है all(), filter(), get(), आदि अपने स्वयं के प्रबंधक बनाना आप पहले से फिल्टर करने के लिए परिणाम और इन सभी एक ही तरीके का है, साथ ही अपने स्वयं के कस्टम विधियों, परिणामों पर काम किया है की अनुमति देता है ।

संपादित करें : मैंने order_by['?']विधि को प्रतिबिंबित करने के लिए अपना कोड संशोधित किया । ध्यान दें कि प्रबंधक एक असीमित संख्या में यादृच्छिक मॉडल लौटाता है। इस वजह से मैंने केवल एक मॉडल प्राप्त करने के तरीके के लिए थोड़ा उपयोग कोड शामिल किया है।

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.

प्रयोग

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

अंत में, आपके पास अपने मॉडल पर कई प्रबंधक हो सकते हैं, इसलिए LeastViewsManager()या बनाने के लिए स्वतंत्र महसूस करें MostPopularManager()


3
यदि आपके pks लगातार हैं, तो get () का उपयोग केवल तभी किया जाएगा, अर्थात आप कभी भी कोई आइटम नहीं हटाते हैं। अन्यथा आप कोशिश कर सकते हैं और एक pk प्राप्त करें जो मौजूद नहीं है। .All () [random_index] का उपयोग करने से इस समस्या का सामना नहीं करना पड़ता है और यह कम कुशल नहीं है।
डैनियल रोज़मैन

मैं समझ गया कि यही वजह है कि मेरा उदाहरण केवल एक प्रबंधक के साथ प्रश्न के कोड की नकल करता है। यह अभी भी ओपी के लिए अपनी सीमा की जाँच का काम करेगा।
सोवियुत

1
उपयोग करने के बजाय .get (id = random_index) का उपयोग करना बेहतर नहीं होगा .filter (id__gte = random_index) [0: 1]? सबसे पहले, यह गैर-लगातार pks के साथ समस्या को हल करने में मदद करता है। दूसरा, get_query_set को वापस आना चाहिए ... एक QuerySet। और आपके उदाहरण में, यह नहीं है।
निकोलस डुमाज़ेट

2
मैं सिर्फ एक विधि घर में एक नया प्रबंधक नहीं बनाऊंगा। मैं "get_random" को डिफ़ॉल्ट प्रबंधक में जोड़ दूंगा ताकि आपको सभी के माध्यम से नहीं जाना पड़े () [0] हर बार आपको यादृच्छिक छवि की आवश्यकता होती है। इसके अलावा, अगर लेखक एक यूजर मॉडल के लिए एक विदेशी व्यक्ति थे, तो आप user.painting_set.get_random () कह सकते हैं।
एंट्टी रासिनें ५

मैं आमतौर पर एक नया प्रबंधक बनाता हूं जब मैं एक कंबल कार्रवाई चाहता हूं, जैसे यादृच्छिक रिकॉर्ड की सूची प्राप्त करना। यदि मैं पहले से मौजूद रिकॉर्ड के साथ अधिक विशिष्ट कार्य कर रहा था, तो मैं डिफ़ॉल्ट प्रबंधक पर एक विधि बनाऊंगा।
सोवियुत

6

अन्य उत्तर या तो संभावित रूप से धीमे (उपयोग order_by('?')) हैं या एक से अधिक SQL क्वेरी का उपयोग करते हैं। यहाँ एक नमूना समाधान है जिसमें कोई ऑर्डर नहीं है और सिर्फ एक क्वेरी है (पोस्टग्रेज मानते हुए):

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

ध्यान रखें कि यदि तालिका खाली है तो यह एक इंडेक्स एरर को बढ़ाएगा। उसके लिए जाँच करने के लिए अपने आप को एक मॉडल-एग्नोस्टिक हेल्पर फ़ंक्शन लिखें।


अवधारणा का एक अच्छा सबूत, लेकिन यह दो प्रश्न हैं और साथ ही डेटाबेस के अंदर, जो आप बचाते हैं वह डेटाबेस के लिए एक राउंडट्रिप है। लेखन के लिए और इसके लायक कच्ची क्वेरी बनाए रखने के लिए आपको इसे कई बार निष्पादित करना होगा। और अगर आप खाली तालिकाओं के खिलाफ पहरा देना चाहते हैं, तो आप count()कच्ची क्वेरी के साथ अग्रिम रूप से और बस चला सकते हैं ।
दोनों

2

बस एक सरल विचार है कि मैं इसे कैसे करूँ:

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

1

बस (काफी सामान्य) विशेष मामले पर ध्यान देने के लिए, अगर कोई हटाए गए तालिका के साथ अनुक्रमित ऑटो-वृद्धि कॉलम है, तो यादृच्छिक चयन करने का इष्टतम तरीका एक क्वेरी है:

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

यह मानती है कि तालिका के लिए आईडी नाम का एक कॉलम है। Django में आप यह कर सकते हैं:

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

जिसमें आपको अपने एप्लिकेशन नाम के साथ appname बदलना होगा।

सामान्य में, एक आईडी कॉलम के साथ, order_by ('?') के साथ बहुत तेजी से किया जा सकता है:

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

1

यह एक रिलेशनल डेटाबेस से एक यादृच्छिक पंक्ति प्राप्त कर रहा है

क्योंकि django orm का उपयोग इस तरह के कार्य को करने के लिए किया जाता है, जो आपके db सर्वर को विशेष रूप से क्रोधित करता है यदि आपके पास बड़ा डेटा टेबल है: |

और समाधान एक मॉडल प्रबंधक प्रदान करता है और हाथ से SQL क्वेरी लिखता है;)

अपडेट :

एक अन्य समाधान जो किसी भी डेटाबेस पर काम करता है वह भी कस्टम लिखने के बिना गैर-राहत वाले लोगों का समर्थन करता है ModelManagerDjango में एक क्वेरी से यादृच्छिक वस्तुओं को प्राप्त करना


1

आप उसी दृष्टिकोण का उपयोग करना चाह सकते हैं, जिसका उपयोग आप किसी भी पुनरावृत्ति के नमूने के लिए करेंगे, खासकर यदि आप नमूना सेट बनाने के लिए कई वस्तुओं को नमूना करने की योजना बनाते हैं । @MatijnPieters और @DzinX ने इस पर बहुत सोचा:

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's और DxinX का समाधान उन डेटा सेटों के लिए है जो बिना किसी यादृच्छिक पहुँच के प्रदान करते हैं। डेटा सेट जो करते हैं (और SQL करता है OFFSET) के लिए, यह अनावश्यक रूप से अक्षम है।
दोनों को

@EndreBoth वास्तव में। मुझे डेटा स्रोत के बावजूद समान दृष्टिकोण का उपयोग करने की कोडिंग "दक्षता" पसंद है। कभी-कभी डेटा सैंपलिंग दक्षता अन्य प्रक्रियाओं द्वारा सीमित पाइपलाइन के प्रदर्शन को महत्वपूर्ण रूप से प्रभावित नहीं करती है (जो आप वास्तव में एमएल प्रशिक्षण की तरह डेटा के साथ कर रहे हैं)।
होबर्स

1

इसके लिए एक बहुत ही आसान दृष्टिकोण में रुचि के रिकॉर्डसेट को केवल फ़िल्टर करना और random.sampleआपको जितने चाहें का चयन करना शामिल है:

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_querysetखाली नहीं है; random.sampleरिटर्न ValueError: sample larger than populationअगर पहले तर्क में बहुत कम तत्व हैं।


2
क्या इससे पूरी क्वेरी सेट हो जाएगी?
पेरुरहंटर

@perrohunter यह भी काम नहीं करेगा Queryset(कम से कम पायथन 3.7 और Django 2.1 के साथ); आपको इसे पहले एक सूची में बदलना होगा, जो स्पष्ट रूप से पूरे क्वेरीसेट को पुनः प्राप्त करता है।
दोनों

@EndreBoth - यह 2016 में लिखा गया था, जब दोनों में से कोई भी अस्तित्व में नहीं था।
आइकनाल

इसलिए मैंने संस्करण की जानकारी जोड़ी। लेकिन अगर इसने 2016 में काम किया, तो उसने पूरी क्वेरी को एक सूची में खींचकर ऐसा किया, है ना?
दोनों

@EndreBoth सही।
पलक

1

नमस्ते मुझे एक क्वेरी से एक यादृच्छिक रिकॉर्ड का चयन करने की आवश्यकता है जिसकी लंबाई मुझे भी रिपोर्ट करने की आवश्यकता है (अर्थात वेब पेज वर्णित आइटम का उत्पादन किया और कहा रिकॉर्ड छोड़ दिया गया है)

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

आधा लंबा (0.7s बनाम 1.7s) लिया गया:

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

मैं अनुमान लगा रहा हूं कि यह यादृच्छिक प्रविष्टि का चयन करने से पहले पूरी क्वेरी को नीचे खींचने से बचता है और मेरे सिस्टम को एक पृष्ठ के लिए पर्याप्त रूप से उत्तरदायी बनाता है जो दोहराए जाने वाले कार्य के लिए बार-बार एक्सेस किया जाता है जहां उपयोगकर्ता आइटम_काउंट काउंट डाउन देखना चाहते हैं।


0

ऑटो-इन्क्रिमेंटिंग प्राथमिक कुंजी के लिए विधि जिसमें कोई हटा नहीं है

यदि आपके पास एक मेज है जहां प्राथमिक कुंजी एक अनुक्रमिक पूर्णांक है जिसमें कोई अंतराल नहीं है, तो निम्न विधि काम करना चाहिए:

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

यह विधि यहां अन्य विधियों की तुलना में बहुत अधिक कुशल है जो तालिका की सभी पंक्तियों के माध्यम से पुनरावृति करती है। जबकि इसके लिए दो डेटाबेस प्रश्नों की आवश्यकता होती है, दोनों तुच्छ हैं। इसके अलावा, यह सरल है और किसी भी अतिरिक्त वर्गों को परिभाषित करने की आवश्यकता नहीं है। हालाँकि, यह प्रयोज्यता एक ऑटो-इन्क्रिमिंग प्राथमिक कुंजी के साथ तालिकाओं तक सीमित है जहां पंक्तियों को कभी भी हटाया नहीं गया है, जैसे कि आईडी के अनुक्रम में कोई अंतराल नहीं है।

ऐसे मामलों में जहां पंक्तियों को हटा दिया गया है जैसे कि अंतराल हैं, यह विधि तब भी काम कर सकती है यदि यह तब तक पुनर्प्राप्त किया जाता है जब तक कि मौजूदा प्राथमिक कुंजी यादृच्छिक रूप से चयनित न हो।

संदर्भ


0

मुझे बहुत सरल समाधान मिला, कस्टम मैनेजर बनाएं:

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

और फिर मॉडल में जोड़ें:

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

अब, आप इसका उपयोग कर सकते हैं:

Example.objects.random()

यादृच्छिक आयात पसंद से
एडम स्टारर

3
कृपया, इस विधि का उपयोग न करें, यदि आप गति चाहते हैं। यह समाधान बहुत धीमा है। मैंने देख लिया है। यह order_by('?').first()60 से अधिक बार धीमा होता है ।
लैग्रेंज

@ Alex78191 नहीं, "?" बहुत बुरा है, लेकिन मेरी विधि EXTRA धीमी है। मैंने शीर्ष उत्तर समाधान का उपयोग किया।
लगरेंज
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.