How to get a random element of the filtered queryset efficiently?

Viewed 168

I try to pull a random object from the already filtered queryset this way:

Product.objects.filter(active=True).order_by('?').first()

Currently it is solved with .order_by('?') but it seems to be very slow.

I try to implement the solution of this question to my view.

It does not work so I am not sure if I am doing it the right way.

products = Product.objects.filter(
    active=True,
)
count = products.aggregate(count=Count('id'))['count']
random_index = randint(0, count - 1)
product = products[random_index]

This is my code now and it throws the ValueError:

ValueError: low >= high

Please, help me to speed it up.

2 Answers

you can do this:

import random

products = Product.objects.filter(
    active=True,
)
items = list(products)

# if you want only a single random item
random_item = random.choice(items)  

or this one:

from random import randint

count = Product.objects.count()

try:
    random_choice = randint(0, count - 1)
except:
    # if count == 0 then it will raise a ValueError.
    random_choice = 0  

random_object = Product.objects.none()
if random_choice > 0:
    random_object = Product.objects.all()[random_choice]  

P.s1: Second solution is more efficient.
P.s2: Don't use order_by('?').

If .order_by('?') is too slow, a decent option (depending on the number of products) might be to query for the PKs of the items, then pick one at random:

qs = Product.objects.filter(active=True)
pks = list(qs.values_list('id', flat=True))
random_pk = random.choice(active_product_pks)
random_item = qs.get(pk=random_pk)

You could cache pks in the Django cache for a while, too, if that suits your business requirements.

Related