Jeg vil foreslå noget, der ligner det, e4c5 foreslog , men jeg ville også:
-
Generer et indeks på datoen for rækkerne, så opnåelse af alle rækkerne på en enkelt dag kan optimeres.
-
Marker datoen og eleven som
unique_together
. Dette forhindrer muligheden for at optage to rangeringer for den samme elev på samme dato.
Modellerne ville se sådan ud:
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()
I en fuldgyldig applikation ville jeg også forvente at have nogle unikke begrænsninger på Grade
og Student
men problemet præsenteret i spørgsmålet giver ikke nok detaljer om disse modeller.
Du kan derefter køre en opgave hver dag med cron
eller hvilken opgavemanager du vil bruge (Selleri er også en mulighed), for at køre en kommando som den følgende, der ville opdatere rækkerne i henhold til en eller anden beregning og rense de gamle poster. Følgende kode er en illustration hvordan det kan gøres. Den rigtige kode bør være designet til at være generelt idempotent (følgende kode er ikke fordi rangberegningen er tilfældig), så hvis serveren genstartes midt i en opdatering, kan kommandoen bare køres igen. Her er koden:
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()
Hvorfor ikke pickles?
Flere årsager:
-
Det er en for tidlig optimering, og overordnet set nok slet ikke en optimering. Nogle operationer kan være hurtigere, men andre operationer vil være langsommere. Hvis rækkerne er syltet ind i et felt på
Student
så betyder indlæsning af en specifik elev i hukommelsen at indlæse al ranginformation i hukommelsen sammen med den pågældende elev. Dette kan afhjælpes ved at bruge.values()
eller.values_list()
men så får du ikke længereStudent
instanser fra databasen. Hvorfor haveStudent
instanser i første omgang og ikke kun få adgang til den rå database? -
Hvis jeg ændrer felterne i
Rank
, Djangos migreringsfaciliteter gør det nemt at udføre de nødvendige ændringer, når jeg implementerer den nye version af min applikation. Hvis ranginformationen er syltet ind i et felt, skal jeg administrere enhver strukturændring ved at skrive tilpasset kode. -
Databasesoftwaren kan ikke få adgang til værdier i en pickle, og du skal derfor skrive brugerdefineret kode for at få adgang til dem. Med modellen ovenfor, hvis du ønsker at liste elever efter rang i dag (og rangerne for i dag er allerede blevet beregnet), så kan du gøre:
for r in Rank.objects.filter(date=utcnow()).order_by("value")\ .prefetch_related(): print r.student.name
Hvis du bruger pickles, skal du scanne alle
Students
og ophæv rækkerne for at udtrække den til den dag, du ønsker, og brug derefter en Python-datastruktur til at sortere eleverne efter rang. Når dette er gjort, skal du gentage denne struktur for at få navnene i orden.