¿Cómo extraer un registro aleatorio utilizando el ORM de Django?

Tengo un modelo que representa pinturas que presento en mi sitio. En la página web principal, me gustaría mostrar algunos de ellos: el más reciente, el que no se ha visitado durante más tiempo, el más popular y el aleatorio.

Estoy usando Django 1.0.2.

Mientras que los primeros 3 de ellos son fáciles de usar con los modelos django, el último (aleatorio) me causa algunos problemas. Puedo ofcodificarlo desde mi punto de vista, a algo como esto:

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

En mi opinión, no parece algo que me gustaría tener. Esto es parte de la abstracción de la base de datos y debería estar en el modelo. Además, aquí necesito encargarme de los registros eliminados (entonces el número de todos los registros no me cubrirá todos los valores clave posibles) y probablemente muchas otras cosas.

¿Alguna otra opción de cómo puedo hacerlo, preferiblemente de alguna manera dentro de la abstracción del modelo?

Related of "¿Cómo extraer un registro aleatorio utilizando el ORM de Django?"

El uso de order_by('?') el servidor db en el segundo día de producción. Una mejor manera es algo como lo que se describe en Obtención de una fila aleatoria de una base de datos relacional .

 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] 

Simplemente use:

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

Está documentado en QuerySet API .

Las soluciones con order_by (‘?’) [: N] son ​​extremadamente lentas incluso para tablas de tamaño mediano si usa MySQL (no conoce otras bases de datos).

order_by('?')[:N] se traducirá a SELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT N consulta.

Esto significa que para cada fila de la tabla se ejecutará la función RAND (), luego se ordenará toda la tabla según el valor de esta función y luego se devolverán los primeros N registros. Si tus mesas son pequeñas, esto está bien. Pero en la mayoría de los casos esta es una consulta muy lenta.

Escribí una función simple que funciona incluso si los identificadores tienen agujeros (algunas filas fueron eliminadas):

 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] 

Es más rápido que order_by (‘?’) En casi todos los casos.

Podría crear un administrador en su modelo para hacer este tipo de cosas. Para entender primero qué es un administrador, el método Painting.objects es un administrador que contiene all() , filter() , get() , etc. Crear su propio administrador le permite pre-filtrar resultados y tener todos estos mismos métodos, Además de tus propios métodos personalizados, trabaja en los resultados.

EDITAR : modifiqué mi código para reflejar el order_by['?'] . Tenga en cuenta que el administrador devuelve un número ilimitado de modelos aleatorios. Debido a esto, he incluido un poco de código de uso para mostrar cómo obtener un solo modelo.

 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. 

Uso

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

Por último, puede tener muchos administradores en sus modelos, así que no dude en crear un LeastViewsManager() o MostPopularManager() .

Aquí hay una solución simple:

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

Las otras respuestas son potencialmente lentas (usando order_by('?') ) O usan más de una consulta SQL. Aquí hay una solución de ejemplo sin pedido y solo una consulta (asumiendo Postgres):

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

Tenga en cuenta que esto generará un error de índice si la tabla está vacía. Escriba usted mismo una función de ayudante modelo-agnóstica para verificar eso.

Sólo una simple idea de cómo lo hago:

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

Creé un modelo de gerente

modelos.py (ejemplo)

 from django.db import models class RandomManager(models.Manager): def get_random(self, items=1): ''' items is integer value By default it returns 1 random item ''' if isinstance(items, int): return self.model.objects.order_by('?')[:items] return self.all() class Category(models.Model): name = models.CharField(max_length=100) objects = RandomManager() class Meta: default_related_name = 'categories' verbose_name = 'category' verbose_name_plural = 'categories' 

Y puede obtener elementos aleatorios de la base de datos, por ejemplo

 Category.objects.get_random(5) # To get 5 random items 

Se recomienda encarecidamente obtener una fila aleatoria de una base de datos relacional

Debido a que usar django orm para hacer algo así, hará que su servidor db se enoje especialmente si tiene una tabla de big data: |

Y la solución es proporcionar un Administrador de modelos y escribir la consulta SQL a mano;)

Actualización :

Otra solución que funciona en cualquier base de datos de backend, incluso las que no son rel, sin escribir el ModelManager personalizado. Obteniendo objetos aleatorios de un Queryset en Django

Es posible que desee utilizar el mismo enfoque que usaría para muestrear cualquier iterador, especialmente si planea muestrear múltiples elementos para crear un conjunto de muestra . @MatijnPieters y @DzinX han pensado mucho en esto:

 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 

Un enfoque mucho más fácil para esto implica simplemente filtrar hacia abajo al conjunto de registros de interés y usar random.sample para seleccionar tantos como desee:

 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 

Tenga en cuenta que debe tener algún código para verificar que my_queryset no esté vacío; random.sample devuelve ValueError: sample larger than population si el primer argumento contiene muy pocos elementos.

Hola, necesitaba seleccionar un registro aleatorio de un queryset cuya longitud también necesitaba informar (es decir, la página web produjo el elemento descrito y dejó los registros)

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

tomó la mitad de tiempo (0.7s vs 1.7s) como:

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

Supongo que esto evita la extracción de toda la consulta antes de seleccionar la entrada aleatoria y hace que mi sistema sea lo suficientemente sensible para una página a la que se accede repetidamente para una tarea repetitiva en la que los usuarios desean ver la cuenta atrás de item_count.

Solo para observar un caso especial (bastante común), si hay una columna de incremento automático indexada en la tabla sin eliminaciones, la forma óptima de hacer una selección aleatoria es una consulta como:

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

que asume una columna llamada id para la tabla. En django puedes hacer esto por:

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

en el que debe reemplazar appname con el nombre de su aplicación.

En General, con una columna de identificación, el order_by (‘?’) Se puede hacer mucho más rápido con:

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

Tengo una solución muy simple, hacer administrador personalizado:

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

y luego agregar en el modelo:

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

Ahora, puedes usarlo:

 Example.objects.random()