web-dev-qa-db-fra.com

Comment extraire un enregistrement aléatoire en utilisant l'ORM de Django?

J'ai un modèle qui représente des peintures que je présente sur mon site. Sur la page principale, j'aimerais en montrer quelques-unes: les plus récentes, les moins visitées, les plus populaires et les plus aléatoires.

J'utilise Django 1.0.2.

Alors que les 3 premiers sont faciles à extraire avec Django modèles, le dernier (aléatoire) me cause quelques ennuis. Je peux le coder à mon avis, à peu près comme ceci:

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

À mon avis, cela ne ressemble pas à quelque chose que j'aimerais avoir; cela fait entièrement partie de l'abstraction de la base de données et devrait figurer dans le modèle. De plus, ici, je dois m'occuper des enregistrements supprimés (le nombre total d'enregistrements ne me couvrira donc pas de toutes les valeurs de clé possibles) et probablement de nombreuses autres choses.

Toutes les autres options, comment puis-je le faire, de préférence dans l'abstraction du modèle?

166
kender

Utiliser order_by('?') tuera le serveur de base de données le deuxième jour de production. Une meilleure façon est quelque chose comme ce qui est décrit dans Obtenir une ligne aléatoire à partir d'une base de données relationnelle .

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]
156
Emil Ivanov

Utilisez simplement:

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

Il est documenté dans QuerySet API .

240
muhuk

Les solutions avec order_by ('?') [: N] sont extrêmement lentes, même pour les tables de taille moyenne si vous utilisez MySQL (je ne connais pas les autres bases de données).

order_by('?')[:N] sera traduit en requête SELECT ... FROM ... WHERE ... ORDER BY Rand() LIMIT N.

Cela signifie que pour chaque ligne de la table, la fonction Rand () sera exécutée, puis la table entière sera triée en fonction de la valeur de cette fonction, puis N premiers enregistrements seront renvoyés. Si vos tables sont petites, c'est très bien. Mais dans la plupart des cas, la requête est très lente.

J'ai écrit une fonction simple qui fonctionne même si les identifiants ont des trous (certaines lignes ont été supprimées):

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]

Il est plus rapide que order_by ('?') Dans presque tous les cas.

26
Mikhail Korobov

Voici une solution simple:

from random import randint

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

Vous pouvez créer un manager sur votre modèle pour faire ce genre de chose. Pour d'abord comprendre ce qu'est un gestionnaire, la méthode Painting.objects Est un gestionnaire qui contient all(), filter(), get(), etc. manager vous permet de pré-filtrer les résultats et d'avoir toutes ces mêmes méthodes, ainsi que vos propres méthodes personnalisées, à travailler sur les résultats.

[~ # ~] edit [~ # ~] : J'ai modifié mon code pour refléter la méthode order_by['?']. Notez que le gestionnaire renvoie un nombre illimité de modèles aléatoires. À cause de cela, j'ai inclus un peu de code d'utilisation pour montrer comment obtenir un seul modèle.

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.

Utilisation

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

Enfin, vous pouvez avoir plusieurs gestionnaires sur vos modèles, alors n'hésitez pas à créer un LeastViewsManager() ou un MostPopularManager().

10
Soviut

Les autres réponses sont potentiellement lentes (avec order_by('?')) ou utilisent plusieurs requêtes SQL. Voici un exemple de solution sans commande et avec une seule requête (en supposant que Postgres):

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

Sachez que cela provoquera une erreur d'index si la table est vide. Ecrivez-vous une fonction d’assistance modèle indépendante pour vérifier cela.

5
Nelo Mitranim

J'ai créé un gestionnaire de modèle

models.py (exemple)

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'

Et vous pouvez obtenir des éléments aléatoires de la base de données par exemple

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

J'ai eu une solution très simple, faire un gestionnaire personnalisé:

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

puis ajoutez dans le modèle:

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

Maintenant, vous pouvez l'utiliser:

Example.objects.random()
2
LagRange

Juste une idée simple comment je le fais:

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

Ceci est hautement recommandé Obtenir une ligne aléatoire d'une base de données relationnelle

Parce que utiliser Django orm pour faire une telle chose, votre serveur de base de données sera particulièrement en colère si vous avez un grand tableau de données: |

Et la solution consiste à fournir un gestionnaire de modèle et à écrire la requête SQL à la main;)

Mise à jour :

Une autre solution qui fonctionne sur n’importe quel backend de base de données, même non rel, sans écrire ModelManagerObtention d'objets aléatoires à partir d'un ensemble de requêtes dans Django

1
Alireza Savand

Il suffit de noter un cas spécial (assez commun), s'il existe une colonne indexée à incrémentation automatique dans la table sans suppression, le moyen optimal de faire une sélection aléatoire est une requête du type:

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

cela suppose une telle colonne nommée id pour table. Dans Django vous pouvez le faire en:

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

dans lequel vous devez remplacer appname par votre nom d'application.

En général, avec une colonne id, order_by ('?') Peut être fait beaucoup plus rapidement avec:

Paiting.objects.raw(
        'SELECT * FROM auth_user WHERE id>=Rand() * (SELECT MAX(id) FROM auth_user) LIMIT %d' 
    % needed_count)
1
Amir Ali Akbari

Une approche beaucoup plus simple consiste simplement à filtrer le jeu d’enregistrements qui vous intéresse et à utiliser random.sample pour en sélectionner autant que vous le souhaitez:

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

Notez que vous devriez avoir du code en place pour vérifier que my_queryset n'est pas vide; random.sample résultats ValueError: sample larger than population si le premier argument contient trop peu d'éléments.

1
eykanal

Bonjour, je devais sélectionner un enregistrement aléatoire dans un ensemble de requêtes dont la longueur devait également être signalée (c.-à-d. Page Web produite, élément décrit et éléments à gauche)

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

a pris deux fois moins de temps (0,7 vs 1,7) que:

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

Je suppose que cela évite d’avoir à parcourir toute la requête avant de sélectionner l’entrée aléatoire et de rendre mon système suffisamment réactif pour une page à laquelle on accède de manière répétée pour une tâche répétitive pour laquelle les utilisateurs souhaitent voir le décompte item_count.

1
pjmnoble

Vous voudrez peut-être utiliser la même approche que vous utiliseriez pour échantillonner un itérateur, en particulier si vous prévoyez d'échantillonner plusieurs éléments pour créer un ensemble d'échantillons . @MatijnPieters et @DzinX ont beaucoup réfléchi à cela:

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
1
hobs

Méthode pour l'incrémentation automatique de la clé primaire sans suppression

Si vous avez une table où la clé primaire est un entier séquentiel sans espace, la méthode suivante devrait alors fonctionner:

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

Cette méthode est beaucoup plus efficace que les autres méthodes ici qui parcourent toutes les lignes de la table. Bien que cela nécessite deux requêtes de base de données, les deux sont triviales. De plus, c'est simple et ne nécessite pas de définir des classes supplémentaires. Cependant, son applicabilité est limitée aux tables avec une clé primaire auto-incrémentée où les lignes n'ont jamais été supprimées, de sorte qu'il n'y ait pas de lacunes dans la séquence d'identifiants.

Dans le cas où des lignes ont été supprimées, telles que des espaces vides, cette méthode peut toujours fonctionner si elle est réessayée jusqu'à ce qu'une clé primaire existante soit sélectionnée de manière aléatoire.

Les références

0