J'ai un modèle qui représente des peintures que je présente sur mon site. Sur la page Web principale, j'aimerais en montrer quelques-uns: le plus récent, celui qui n'a pas été visité la plupart du temps, le plus populaire et un aléatoire.
J'utilise Django 1.0.2.
Alors que les 3 premiers d'entre eux sont faciles à tirer en utilisant les modèles django, le dernier (aléatoire) me cause des problèmes. Je peux ofc le coder à mon avis, à quelque chose 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)
Cela ne ressemble pas à quelque chose que j'aimerais avoir à mon avis - cela fait entièrement partie de l'abstraction de la base de données et devrait être dans le modèle. De plus, ici, je dois m'occuper des enregistrements supprimés (alors le nombre de tous les enregistrements ne me couvrira pas toutes les valeurs clés possibles) et probablement beaucoup d'autres choses.
D'autres options comment je peux le faire, de préférence d'une manière ou d'une autre à l'intérieur de l'abstraction du modèle?
la source
Réponses:
L'utilisation
order_by('?')
tuera le serveur db le deuxième jour de production. Un meilleur moyen est quelque chose comme ce qui est décrit dans Obtenir une ligne aléatoire à partir d'une base de données relationnelle .la source
model.objects.aggregate(count=Count('id'))['count']
overmodel.objects.all().count()
.all()[randint(0, count - 1)]
en effet. Peut-être devriez-vous vous concentrer sur l'identification de la partie de la réponse qui est fausse ou faible, plutôt que de redéfinir «l'erreur par erreur» pour nous et de crier après les électeurs insensés. (C'est peut-être qu'il n'utilise pas.objects
?)Utilisez simplement:
Il est documenté dans l' API QuerySet .
la source
random.choice(Model.objects.all())
?Les solutions avec order_by ('?') [: N] sont extrêmement lentes même pour les tables de taille moyenne si vous utilisez MySQL (ne connaissez pas les autres bases de données).
order_by('?')[:N]
sera traduit enSELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT N
requête.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 les N premiers enregistrements seront renvoyés. Si vos tables sont petites, c'est très bien. Mais dans la plupart des cas, il s'agit d'une requête 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):
C'est plus rapide que order_by ('?') Dans presque tous les cas.
la source
Voici une solution simple:
la source
Vous pouvez créer un gestionnaire sur votre modèle pour faire ce genre de chose. D'abord comprendre ce qu'est un gestionnaire est, la
Painting.objects
méthode est un gestionnaire qui contientall()
,filter()
,get()
, etc. Créer votre propre gestionnaire vous permet d' effectuer une pré-filtrage des résultats et ont tous les mêmes méthodes, ainsi que vos propres méthodes personnalisées, le travail sur les résultats .EDIT : j'ai modifié mon code pour refléter la
order_by['?']
méthode. Notez que le gestionnaire renvoie un nombre illimité de modèles aléatoires. Pour cette raison, j'ai inclus un peu de code d'utilisation pour montrer comment obtenir un seul modèle.Usage
Enfin, vous pouvez avoir de nombreux managers sur vos modèles, alors n'hésitez pas à créer un
LeastViewsManager()
ouMostPopularManager()
.la source
Les autres réponses sont potentiellement lentes (utilisation
order_by('?')
) ou utilisent plus d'une requête SQL. Voici un exemple de solution sans ordre et avec une seule requête (en supposant Postgres):Sachez que cela provoquera une erreur d'index si la table est vide. Écrivez-vous une fonction d'assistance indépendante du modèle pour vérifier cela.
la source
count()
requête à l'avance et vous passer de la requête brute.Juste une idée simple comment je le fais:
la source
Juste pour noter un cas particulier (assez courant), s'il y a une colonne auto-incrémentée indexée dans la table sans suppression, la façon optimale de faire une sélection aléatoire est une requête comme:
qui suppose une telle colonne nommée id pour table. Dans django, vous pouvez le faire en:
dans lequel vous devez remplacer appname par le nom de votre application.
En général, avec une colonne id, le order_by ('?') Peut être fait beaucoup plus rapidement avec:
la source
Ceci est fortement recommandé
Obtenir une ligne aléatoire à partir d'une base de données relationnelleParce que l'utilisation de django orm pour faire une telle chose rendra votre serveur db en colère, surtout si vous avez une table Big Data: |
Et la solution est de fournir un Model Manager et d'écrire la requête SQL à la main;)
Mettre à jour :
Une autre solution qui fonctionne sur n'importe quel backend de base de données, même non-rel, sans écrire personnalisé
ModelManager
. Obtenir des objets aléatoires à partir d'un ensemble de requêtes dans Djangola source
Vous souhaiterez peut-être utiliser la même approche que celle que vous utiliseriez pour échantillonner n'importe quel 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 à ceci:
la source
OFFSET
), cela est inefficace.Une approche beaucoup plus simple consiste simplement à filtrer le jeu d'enregistrements d'intérêt et à
random.sample
en sélectionner autant que vous le souhaitez:Notez que vous devez avoir du code en place pour vérifier qu'il
my_queryset
n'est pas vide;random.sample
renvoieValueError: sample larger than population
si le premier argument contient trop peu d'éléments.la source
Queryset
(au moins avec Python 3.7 et Django 2.1); vous devez d'abord le convertir en une liste, qui récupère évidemment l'ensemble de la requête.Salut, j'avais besoin de sélectionner un enregistrement aléatoire à partir d'un ensemble de requêtes dont la longueur que je devais également signaler (c'est-à-dire que la page Web a produit l'élément décrit et lesdits enregistrements sont restés)
a pris deux fois moins de temps (0,7 s contre 1,7 s) que:
J'imagine que cela évite de tirer toute la requête avant de sélectionner l'entrée aléatoire et rend mon système suffisamment réactif pour une page consultée à plusieurs reprises pour une tâche répétitive où les utilisateurs veulent voir le compte à rebours item_count.
la source
Méthode d'auto-incrémentation de la clé primaire sans suppression
Si vous avez une table où la clé primaire est un entier séquentiel sans espaces, la méthode suivante devrait fonctionner:
Cette méthode est beaucoup plus efficace que les autres méthodes ici qui itèrent sur 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 la définition de 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 a pas de lacunes dans la séquence d'identifiants.
Dans le cas où des lignes ont été supprimées comme des espaces, cette méthode peut toujours fonctionner si elle est réessayée jusqu'à ce qu'une clé primaire existante soit sélectionnée au hasard.
Références
la source
J'ai une solution très simple, créer un gestionnaire personnalisé:
puis ajoutez le modèle:
Maintenant, vous pouvez l'utiliser:
la source
order_by('?').first()
plus de 60 fois.