update select_related queryset query not example create python django django-models

python - select_related - ¿Cómo extraer un registro aleatorio usando el ORM de Django?



select_related django example (12)

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 nuevo, uno que no se visitó durante la mayor parte del tiempo, el más popular y otro al azar.

Estoy usando Django 1.0.2.

Mientras que los primeros 3 de ellos son fáciles de tirar usando modelos django, el último (al azar) me causa algunos problemas. No puedo codificarlo en mi opinión, 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)

No parece algo que me gustaría tener en mi opinión, esto es parte de la abstracción de la base de datos y debería estar en el modelo. Además, aquí tengo que encargarme de los registros eliminados (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?


¿Qué hay de malo en simplemente hacer esto?

import random records = Model.objects.all() random_record = random.choice(records)


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


El uso de order_by(''?'') el servidor de db en el segundo día de producción. Una mejor manera es algo así como lo que se describe en Cómo obtener 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]


Es posible que desee utilizar el mismo enfoque que usaría para muestrear cualquier iterador, especialmente si planea muestrear varios elementos para crear un conjunto de muestra . @MatijnPieters y @DzinX reflexionaron mucho sobre 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://.com/a/12583436/623735 @MartinPieters: https://.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


Esto es altamente recomendable Obtener una fila aleatoria de una base de datos relacional

Porque usar django orm para hacer tal cosa, hará que tu servidor db se enoje especialmente si tienes una gran tabla de datos: |

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 backend de base de datos incluso sin ModelManager sin necesidad de escribir ModelManager personalizado. Obtener objetos aleatorios desde un Queryset en Django


Las otras respuestas son potencialmente lentas (usando order_by(''?'') ) O usan más de una consulta SQL. Aquí hay una solución de muestra sin pedidos 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. Escríbase una función auxiliar agnóstica de modelos para verificar eso.


Las soluciones con order_by (''?'') [: N] son ​​extremadamente lentas incluso para las tablas de tamaño mediano si usa MySQL (no sé sobre otras bases de datos).

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

Significa que para cada fila en la tabla se ejecutará la función RAND (), luego toda la tabla se ordenará según el valor de esta función y luego se devolverán los primeros N registros. Si sus tablas son pequeñas, 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 id 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ías crear un manager en tu 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. La creación de su propio administrador le permite prefiltrar los resultados y tener todos estos mismos métodos, así como sus propios métodos personalizados, trabaje en los resultados.

EDIT : modifiqué mi código para reflejar el order_by[''?''] . Tenga en cuenta que el administrador devuelve una cantidad ilimitada de modelos aleatorios. Debido a esto, he incluido un poco de código de uso para mostrar cómo obtener solo un 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 siéntase libre de crear un LeastViewsManager() o MostPopularManager() .


Simplemente use:

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

Está documentado en QuerySet API .


Solo para observar un caso especial (bastante común), si hay una columna indexada de incremento automático 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 dicha columna llamada id para la tabla. En django puedes hacer esto de la siguiente manera:

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

en el que debe reemplazar el nombre de la aplicación 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)


Solo una idea simple 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]


Un enfoque mucho más fácil para esto implica simplemente filtrar 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 en su lugar 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.