Je suggérerais quelque chose de similaire à ce que e4c5 suggère , mais je voudrais aussi :
-
Générez un index sur la date des classements afin d'optimiser l'obtention de tous les classements sur une seule journée.
-
Marquez la date et l'élève comme
unique_together
. Cela évite la possibilité d'enregistrer deux rangs pour le même élève à la même date.
Les modèles ressembleraient à ceci :
from django.db import models
class Grade(models.Model):
pass # Whatever you need here...
class Student(models.Model):
name = models.CharField(max_length=20)
grade = models.ForeignKey(Grade)
class Rank(models.Model):
class Meta(object):
unique_together = (("date", "student"), )
date = models.DateField(db_index=True)
student = models.ForeignKey(Student)
value = models.IntegerField()
Dans une application à part entière, je m'attendrais également à avoir des contraintes d'unicité sur Grade
et Student
mais le problème présenté dans la question ne fournit pas suffisamment de détails sur ces modèles.
Vous pourriez alors exécuter une tâche tous les jours avec cron
ou quel que soit le gestionnaire de tâches que vous souhaitez utiliser (le céleri est également une option), pour exécuter une commande comme la suivante qui mettrait à jour les classements en fonction de certains calculs et purgerait les anciens enregistrements. Le code suivant est une illustration de la façon dont cela peut être fait. Le vrai code doit être conçu pour être généralement idempotent (le code suivant n'est pas parce que le calcul du rang est aléatoire) de sorte que si le serveur est redémarré au milieu d'une mise à jour, la commande peut simplement être réexécutée. Voici le code :
import random
import datetime
from optparse import make_option
from django.utils.timezone import utc
from django.core.management.base import BaseCommand
from school.models import Rank, Student
def utcnow():
return datetime.datetime.utcnow().replace(tzinfo=utc)
class Command(BaseCommand):
help = "Compute ranks and cull the old ones"
option_list = BaseCommand.option_list + (
make_option('--fake-now',
default=None,
help='Fake the now value to X days ago.'),
)
def handle(self, *args, **options):
now = utcnow()
fake_now = options["fake_now"]
if fake_now is not None:
now -= datetime.timedelta(days=int(fake_now))
print "Setting now to: ", now
for student in Student.objects.all():
# This simulates a rank computation for the purpose of
# illustration.
rank_value = random.randint(1, 1000)
try:
rank = Rank.objects.get(student=student, date=now)
except Rank.DoesNotExist:
rank = Rank(
student=student, date=now)
rank.value = rank_value
rank.save()
# Delete all ranks older than 180 days.
Rank.objects.filter(
date__lt=now - datetime.timedelta(days=180)).delete()
Pourquoi pas des cornichons ?
Plusieurs raisons :
-
C'est une optimisation prématurée, et dans l'ensemble probablement pas une optimisation du tout. Certains les opérations peuvent être plus rapides, mais d'autres opérations sera plus lent. Si les classements sont décalés dans un champ sur
Student
ensuite, charger un élève spécifique en mémoire signifie charger toutes les informations de classement en mémoire avec cet élève. Cela peut être atténué en utilisant.values()
ou.values_list()
mais alors vous n'obtenez plusStudent
instances de la base de données. Pourquoi avoirStudent
instances en premier lieu et pas seulement accéder à la base de données brute ? -
Si je change les champs dans
Rank
, les fonctionnalités de migration de Django permettent facilement d'effectuer les modifications nécessaires lorsque je déploie la nouvelle version de mon application. Si les informations de classement sont conservées dans un champ, je dois gérer tout changement de structure en écrivant un code personnalisé. -
Le logiciel de base de données ne peut pas accéder aux valeurs dans un cornichon et vous devez donc écrire un code personnalisé pour y accéder. Avec le modèle ci-dessus, si vous souhaitez répertorier les étudiants par rang aujourd'hui (et que les rangs d'aujourd'hui ont déjà été calculés), vous pouvez :
for r in Rank.objects.filter(date=utcnow()).order_by("value")\ .prefetch_related(): print r.student.name
Si vous utilisez des cornichons, vous devez scanner tous les
Students
et détachez les rangs pour extraire celui du jour que vous voulez, puis utilisez une structure de données Python pour classer les étudiants par rang. Une fois cela fait, vous devez parcourir cette structure pour obtenir les noms dans l'ordre.